From 7f3680d6a533c3c9aa3c988bad38a2f11ecdcb94 Mon Sep 17 00:00:00 2001 From: Efra Espada Date: Mon, 14 Sep 2026 14:45:13 +0200 Subject: [PATCH 1/6] codex-semantic-github-publication: make GitHub publication semantic and quiet --- action.yml | 54 +- build/api/index.js | 415 +- build/cli/index.js | 12370 +-------------- build/github_action/index.js | 13090 +--------------- docs/bugbot/detection.mdx | 10 +- docs/bugbot/examples.mdx | 17 +- docs/bugbot/failure-scenarios.mdx | 2 +- docs/configuration.mdx | 17 +- docs/development/architecture.mdx | 9 + docs/features.mdx | 33 +- docs/issues/branch-synchronization.mdx | 7 +- docs/issues/comment-commands.mdx | 10 +- docs/issues/configuration.mdx | 69 +- docs/issues/notifications-and-auto-close.mdx | 41 +- docs/issues/type/bugfix.mdx | 22 +- docs/issues/type/chore.mdx | 22 +- docs/issues/type/docs.mdx | 22 +- docs/issues/type/feature.mdx | 22 +- docs/issues/type/hotfix.mdx | 22 +- docs/issues/type/release.mdx | 22 +- docs/pull-requests/capabilities.mdx | 19 +- docs/pull-requests/configuration.mdx | 15 +- docs/pull-requests/examples.mdx | 10 +- docs/pull-requests/workflow-setup.mdx | 4 +- docs/single-actions/available-actions.mdx | 4 +- specs/CATALOG.md | 12 +- specs/catalog.json | 35 +- .../github_action_completion.test.ts | 8 +- src/actions/github_action.ts | 5 +- src/actions/github_action_completion.ts | 2 - .../branch_sync_notification_policy.test.ts | 11 + .../bugbot_review_presentation_policy.test.ts | 28 +- .../publication_identity_policy.test.ts | 79 + .../publication_message_catalog.test.ts | 26 + .../result_publication_policy.test.ts | 188 - ...semantic_result_publication_policy.test.ts | 128 + .../branch_sync_notification_policy.ts | 62 +- .../bugbot_review_presentation_policy.ts | 140 +- .../policies/copilot_interaction_policy.ts | 43 +- .../deployment_presentation_policy.ts | 10 + .../policies/publication_identity_policy.ts | 90 + .../policies/publication_message_catalog.ts | 72 + .../policies/result_publication_contracts.ts | 64 - .../policies/result_publication_policy.ts | 67 - .../result_publication_presentation_policy.ts | 37 - .../result_publication_sections_policy.ts | 49 - .../semantic_result_publication_policy.ts | 262 + .../policies/status_command_policy.ts | 25 +- .../ports/issue_lifecycle_ports.ts | 15 +- .../push_single_action_contexts.test.ts | 2 + .../close_inactive_issues_use_case.test.ts | 12 +- .../publish_issue_comment_use_case.test.ts | 3 +- .../actions/close_inactive_issues_workflow.ts | 40 +- .../actions/observe_branch_sync_use_case.ts | 3 +- .../comment_automation_command_workflow.ts | 1 + .../usecases/push_single_action_contexts.ts | 5 + ...detect_potential_problems_use_case.test.ts | 2 +- ...otify_new_commit_on_issue_use_case.test.ts | 225 +- .../mark_findings_resolved_use_case.test.ts | 3 +- ...le_bugbot_review_state_integration.test.ts | 6 +- ...ugbot_review_presentation_use_case.test.ts | 4 +- .../bugbot/publish_findings_use_case.ts | 9 +- .../bugbot/publish_pr_review_comments.ts | 8 +- ...ize_bugbot_review_presentation_use_case.ts | 9 +- .../commit_notification_content_policy.ts | 84 - .../notify_new_commit_on_issue_use_case.ts | 4 +- .../notify_new_commit_on_issue_workflow.ts | 34 +- .../__tests__/publish_resume_use_case.test.ts | 594 +- .../reply_publication_workflow.test.ts | 69 + ...ared_capability_context_projection.test.ts | 3 +- .../status_card_publication_workflow.test.ts | 91 + .../steps/common/publish_resume_use_case.ts | 8 +- .../steps/common/publish_resume_workflow.ts | 259 +- .../common/reply_publication_workflow.ts | 69 + .../status_card_publication_workflow.ts | 78 + ...close_issue_after_merging_use_case.test.ts | 13 +- .../close_not_allowed_issue_use_case.test.ts | 13 +- .../close_issue_after_merging_use_case.ts | 8 +- .../issue/close_not_allowed_issue_use_case.ts | 15 +- .../github_publication_boundaries.test.ts | 68 + .../github_publication_mutation_baseline.json | 15 + .../issue_content_repository.test.ts | 6 +- .../issue/issue_content_repository.ts | 13 +- .../__tests__/github_publication.test.ts | 39 + src/domain/github_publication.ts | 92 + .../lifecycle_capability_port_binding.test.ts | 4 + ...gle_action_capability_port_binding.test.ts | 7 +- .../issue_use_case_composition_root.ts | 5 +- .../lifecycle_capability_port_binding.ts | 15 + .../main_run_route_composition_root.ts | 4 +- ...h_single_action_capability_port_binding.ts | 11 +- .../validate_workflow_contract.test.ts | 19 + src/utils/__tests__/comment_watermark.test.ts | 45 +- src/utils/comment_watermark.ts | 30 +- 94 files changed, 3679 insertions(+), 26154 deletions(-) create mode 100644 src/application/policies/__tests__/publication_identity_policy.test.ts create mode 100644 src/application/policies/__tests__/publication_message_catalog.test.ts delete mode 100644 src/application/policies/__tests__/result_publication_policy.test.ts create mode 100644 src/application/policies/__tests__/semantic_result_publication_policy.test.ts create mode 100644 src/application/policies/publication_identity_policy.ts create mode 100644 src/application/policies/publication_message_catalog.ts delete mode 100644 src/application/policies/result_publication_contracts.ts delete mode 100644 src/application/policies/result_publication_policy.ts delete mode 100644 src/application/policies/result_publication_presentation_policy.ts delete mode 100644 src/application/policies/result_publication_sections_policy.ts create mode 100644 src/application/policies/semantic_result_publication_policy.ts delete mode 100644 src/application/usecases/steps/commit/commit_notification_content_policy.ts create mode 100644 src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts create mode 100644 src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts create mode 100644 src/application/usecases/steps/common/reply_publication_workflow.ts create mode 100644 src/application/usecases/steps/common/status_card_publication_workflow.ts create mode 100644 src/architecture/__tests__/github_publication_boundaries.test.ts create mode 100644 src/architecture/github_publication_mutation_baseline.json create mode 100644 src/domain/__tests__/github_publication.test.ts create mode 100644 src/domain/github_publication.ts diff --git a/action.yml b/action.yml index 79beb8a42..a8cae5db4 100644 --- a/action.yml +++ b/action.yml @@ -343,76 +343,76 @@ inputs: description: "Naming convention for release branches." default: "release" images-on-issue: - description: "Include images in issue comments." - default: "true" + description: "Deprecated compatibility input. Semantic issue messages do not include decorative images." + default: "false" images-on-pull-request: - description: "Include images in pull request comments." - default: "true" + description: "Deprecated compatibility input. Semantic pull request messages do not include decorative images." + default: "false" images-on-commit: - description: "Include images in commit comments." - default: "true" + description: "Deprecated compatibility input. Push events do not include decorative images." + default: "false" images-issue-automatic: - description: "Image URLs to display in automatic action issue comments." + description: "Deprecated issue image URL pool retained for input compatibility." default: "" images-issue-feature: - description: "Image URLs to display in feature action issue comments." + description: "Deprecated issue image URL pool retained for input compatibility." default: "" images-issue-bugfix: - description: "Image URLs to display in bugfix action issue comments." + description: "Deprecated issue image URL pool retained for input compatibility." default: "" images-issue-docs: - description: "Image URLs to display in docs action issue comments." + description: "Deprecated issue image URL pool retained for input compatibility." default: "" images-issue-chore: - description: "Image URLs to display in chore action issue comments." + description: "Deprecated issue image URL pool retained for input compatibility." default: "" images-issue-hotfix: - description: "Image URLs to display in hotfix action issue comments." + description: "Deprecated issue image URL pool retained for input compatibility." default: "" images-issue-release: - description: "Image URLs to display in release action issue comments." + description: "Deprecated issue image URL pool retained for input compatibility." default: "" images-pull-request-automatic: - description: "Image URLs to display in automatic action pull request comments." + description: "Deprecated pull request image URL pool retained for input compatibility." default: "" images-pull-request-feature: - description: "Image URLs to display in feature action pull request comments." + description: "Deprecated pull request image URL pool retained for input compatibility." default: "" images-pull-request-bugfix: - description: "Image URLs to display in bugfix action pull request comments." + description: "Deprecated pull request image URL pool retained for input compatibility." default: "" images-pull-request-docs: - description: "Image URLs to display in docs action pull request comments." + description: "Deprecated pull request image URL pool retained for input compatibility." default: "" images-pull-request-chore: - description: "Image URLs to display in chore action pull request comments." + description: "Deprecated pull request image URL pool retained for input compatibility." default: "" images-pull-request-hotfix: - description: "Image URLs to display in hotfix action pull request comments." + description: "Deprecated pull request image URL pool retained for input compatibility." default: "" images-pull-request-release: - description: "Image URLs to display in release action pull request comments." + description: "Deprecated pull request image URL pool retained for input compatibility." default: "" images-commit-feature: - description: "Image URLs to display in feature action commit comments." + description: "Deprecated commit image URL pool retained for input compatibility." default: "" images-commit-bugfix: - description: "Image URLs to display in bugfix action commit comments." + description: "Deprecated commit image URL pool retained for input compatibility." default: "" images-commit-release: - description: "Image URLs to display in release action commit comments." + description: "Deprecated commit image URL pool retained for input compatibility." default: "" images-commit-automatic: - description: "Image URLs to display in automatic action commit comments." + description: "Deprecated commit image URL pool retained for input compatibility." default: "" images-commit-docs: - description: "Image URLs to display in docs action commit comments." + description: "Deprecated commit image URL pool retained for input compatibility." default: "" images-commit-chore: - description: "Image URLs to display in chore action commit comments." + description: "Deprecated commit image URL pool retained for input compatibility." default: "" images-commit-hotfix: - description: "Image URLs to display in hotfix action commit comments." + description: "Deprecated commit image URL pool retained for input compatibility." default: "" commit-prefix-transforms: description: "List of transformations to apply sequentially for commit prefix generation. Options: replace-slash, replace-all, lowercase, uppercase, kebab-case, snake-case, camel-case, trim, remove-numbers, clean-dashes, etc." diff --git a/build/api/index.js b/build/api/index.js index 77deab4d9..be085a1ac 100644 --- a/build/api/index.js +++ b/build/api/index.js @@ -770,6 +770,7 @@ exports.renderBugbotReviewSnapshot = renderBugbotReviewSnapshot; exports.buildNewBugbotReviewSnapshotHeader = buildNewBugbotReviewSnapshotHeader; const review_state_1 = __nccwpck_require__(9200); const github_comment_publication_policy_1 = __nccwpck_require__(2712); +const publication_identity_policy_1 = __nccwpck_require__(5403); exports.BUGBOT_STATUS_MARKER_PREFIX = 'copilot-bugbot-status'; exports.BUGBOT_REVIEW_MARKER_PREFIX = 'copilot-bugbot-review'; exports.BUGBOT_REVIEW_STATUS_START = '`; +} +function parsePublicationMarker(body) { + if (typeof body !== 'string') + return undefined; + const match = body.match(//u); + if (!match) + return undefined; + const topic = match[1]; + if (!github_publication_1.PUBLICATION_TOPICS.includes(topic)) + return undefined; + const number = Number(match[3]); + if (!Number.isSafeInteger(number) || number < 1) + return undefined; + return Object.freeze({ + identity: Object.freeze({ + topic, + target: Object.freeze({ kind: match[2] === 'pr' ? 'pull-request' : 'issue', number }), + key: match[4], + }), + sourceVersion: match[5], + digest: match[6], + }); +} +function buildPublicationReplyMarker(marker) { + for (const value of [marker.target, marker.correlationId, marker.messageKey]) { + if (!SAFE_VALUE.test(value)) + throw new Error('Publication reply marker contains an unsafe identity value.'); + } + if (!DIGEST.test(marker.digest)) + throw new Error('Publication reply marker contains an invalid digest.'); + return ``; +} +function parsePublicationReplyMarker(body) { + if (typeof body !== 'string') + return undefined; + const match = body.match(//u); + if (!match) + return undefined; + return Object.freeze({ target: match[1], correlationId: match[2], messageKey: match[3], digest: match[4] }); +} +function buildDuplicateMarker(canonicalCommentId) { + if (!Number.isSafeInteger(canonicalCommentId) || canonicalCommentId < 1) { + throw new Error('Canonical comment id must be a positive integer.'); + } + return ``; +} +function stableSerialize(value) { + if (Array.isArray(value)) + return `[${value.map(stableSerialize).join(',')}]`; + if (value && typeof value === 'object') { + const record = value; + return `{${Object.keys(record).sort().map(key => `${JSON.stringify(key)}:${stableSerialize(record[key])}`).join(',')}}`; + } + return JSON.stringify(value) ?? 'null'; +} + + +/***/ }), + +/***/ 4223: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.SPANISH_PUBLICATION_CATALOG = exports.ENGLISH_PUBLICATION_CATALOG = void 0; +exports.resolveStaticPublicationCatalog = resolveStaticPublicationCatalog; +const locale_1 = __nccwpck_require__(5386); +const ENGLISH = Object.freeze({ + locale: 'en-US', + implementationPlan: 'Implementation plan', + planReady: 'Ready to start. No action is required from maintainers before implementation.', + planAcceptance: 'Acceptance', + commandsHint: 'Need something else? Mention the bot with a question or use `/copilot help`.', + progress: 'Progress', + progressState: Object.freeze({ 'not-started': 'not started', 'in-progress': 'in progress', complete: 'complete' }), + currentStatus: 'Current status', + next: 'Next', + noActionRequired: 'No action required.', + supersededStatus: 'This status was superseded by the canonical card.', + viewCurrentStatus: 'View current status', + duplicateReply: 'This duplicate response was suppressed.', + viewOriginalResponse: 'View the original response', +}); +const SPANISH = Object.freeze({ + locale: 'es-ES', + implementationPlan: 'Plan de implementación', + planReady: 'Listo para comenzar. No se requiere ninguna acción de mantenimiento antes de la implementación.', + planAcceptance: 'Aceptación', + commandsHint: '¿Necesitas algo más? Menciona al bot con una pregunta o usa `/copilot help`.', + progress: 'Progreso', + progressState: Object.freeze({ 'not-started': 'sin iniciar', 'in-progress': 'en curso', complete: 'completado' }), + currentStatus: 'Estado actual', + next: 'Siguiente paso', + noActionRequired: 'No se requiere ninguna acción.', + supersededStatus: 'Este estado fue sustituido por la tarjeta canónica.', + viewCurrentStatus: 'Ver estado actual', + duplicateReply: 'Esta respuesta duplicada se ha omitido.', + viewOriginalResponse: 'Ver la respuesta original', +}); +function resolveStaticPublicationCatalog(locale) { + const requestedLocale = (0, locale_1.canonicalizeLocaleTag)(locale || locale_1.DEFAULT_REPOSITORY_LOCALE); + if ((0, locale_1.baseLanguage)(requestedLocale) === 'es') { + return Object.freeze({ requestedLocale, catalog: SPANISH, fallback: false }); + } + if ((0, locale_1.baseLanguage)(requestedLocale) === 'en') { + return Object.freeze({ requestedLocale, catalog: ENGLISH, fallback: false }); + } + return Object.freeze({ requestedLocale, catalog: ENGLISH, fallback: true }); +} +exports.ENGLISH_PUBLICATION_CATALOG = ENGLISH; +exports.SPANISH_PUBLICATION_CATALOG = SPANISH; + + /***/ }), /***/ 6152: @@ -2753,7 +2882,6 @@ function isRecord(value) { */ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.publishFindings = publishFindings; -const comment_watermark_1 = __nccwpck_require__(3623); const finding_1 = __nccwpck_require__(1011); const publish_issue_finding_comment_1 = __nccwpck_require__(4950); const publish_pr_review_comments_1 = __nccwpck_require__(352); @@ -2761,16 +2889,12 @@ const publish_overflow_comment_1 = __nccwpck_require__(974); async function publishFindings(param) { const { operation, context, findings, commitSha, overflowCount = 0, overflowTitles = [], ports } = param; const { existingByFindingId, canonicalPullRequest, prContext } = context; - const watermark = commitSha - ? (0, comment_watermark_1.getCommentWatermark)({ commitSha, owner: operation.repository.owner, repo: operation.repository.name }) - : (0, comment_watermark_1.getCommentWatermark)(); const reviewPublisher = prContext && canonicalPullRequest ? new publish_pr_review_comments_1.PullRequestReviewCommentPublisher({ repository: ports.pullRequestComments, operation, openPrNumber: canonicalPullRequest.number, prContext, - watermark, ruleSources: context.reviewRuleSources, omittedRuleCount: context.omittedReviewRules, }) @@ -2869,7 +2993,7 @@ class PullRequestReviewCommentPublisher { } // Existing comments do not carry enough anchor metadata to prove that a // GitHub suggestion is still attached to a RIGHT-side changed line. - const body = `${(0, bugbot_finding_marker_policy_1.buildCommentBody)(finding, false, undefined, { includeSuggestedChange: false })}\n\n${this.options.watermark}`; + const body = (0, bugbot_finding_marker_policy_1.buildCommentBody)(finding, false, undefined, { includeSuggestedChange: false }); await this.options.repository.updatePullRequestReviewComment(existing.pullRequest.commentIdentity, body); if (existing.pullRequest.resolved || existing.pullRequest.threadResolved === true) { // Persist the open marker before reopening the native thread. This @@ -2883,7 +3007,7 @@ class PullRequestReviewCommentPublisher { const findingBody = (0, bugbot_finding_marker_policy_1.buildCommentBody)(finding, false, undefined, { includeSuggestedChange: allowSuggestedChanges && anchor?.subjectType === 'line' && anchor.side === 'RIGHT', }); - const body = `${findingBody}\n\n${this.options.watermark}`; + const body = findingBody; this.findingsToCreate.push(finding); if (!anchor) { this.unanchoredBodies.push(findingBody); @@ -2910,7 +3034,7 @@ class PullRequestReviewCommentPublisher { if (this.findingsToCreate.length === 0 && overflowCount === 0) return; const { repository, operation, openPrNumber, prContext } = this.options; - await repository.createReviewWithComments(openPrNumber, prContext.prHeadSha, buildReviewSummary(this.findingsToCreate, this.commentsToCreate.length, this.unanchoredBodies, overflowCount, overflowTitles, this.options.watermark, operation.analysis.reviewConfiguration.traceRules + await repository.createReviewWithComments(openPrNumber, prContext.prHeadSha, buildReviewSummary(this.findingsToCreate, this.commentsToCreate.length, this.unanchoredBodies, overflowCount, overflowTitles, operation.analysis.reviewConfiguration.traceRules ? this.options.ruleSources ?? [] : [], operation.analysis.reviewConfiguration.traceRules ? this.options.omittedRuleCount ?? 0 @@ -2949,7 +3073,7 @@ function resolveReviewAnchor(reportedLine, reportedEndLine, reportedPath, contex const fallback = context.prFiles.find((file) => file.status !== 'removed') ?? context.prFiles[0]; return fallback ? { path: fallback.filename, subjectType: 'file' } : undefined; } -function buildReviewSummary(findings, inlineCount, unanchoredBodies, overflowCount, overflowTitles, watermark, ruleSources = [], omittedRuleCount = 0, analyzedHeadSha = 'unknown', locale = 'en-US') { +function buildReviewSummary(findings, inlineCount, unanchoredBodies, overflowCount, overflowTitles, ruleSources = [], omittedRuleCount = 0, analyzedHeadSha = 'unknown', locale = 'en-US') { const findingLines = findings.map((finding) => { const severity = sanitizeSummaryText(finding.severity, 32) || "unspecified"; const title = sanitizeSummaryText(finding.title, 500) || 'Potential problem'; @@ -2985,7 +3109,6 @@ function buildReviewSummary(findings, inlineCount, unanchoredBodies, overflowCou rows.push(`| — | ${omittedRuleCount} omitted by duplicate, empty, or combined-budget policy |`); sections.push(`### Review configuration\n\nRules in effective precedence order:\n\n| Source | Status |\n| --- | --- |\n${rows.join('\n')}`); } - sections.push(watermark); return sections.join("\n\n"); } function sanitizeSummaryText(value, maximum) { @@ -3368,6 +3491,8 @@ exports.synchronizeBugbotReviewPresentation = synchronizeBugbotReviewPresentatio const bugbot_review_presentation_policy_1 = __nccwpck_require__(3799); const bugbot_review_ownership_policy_1 = __nccwpck_require__(3288); const review_projection_1 = __nccwpck_require__(859); +const publication_identity_policy_1 = __nccwpck_require__(5403); +const publication_message_catalog_1 = __nccwpck_require__(4223); const MAX_REVIEW_UPDATES_PER_RUN = 20; const REVIEW_UPDATE_CONCURRENCY = 4; /** @@ -3450,10 +3575,13 @@ async function synchronizeStatusCard(input, projection, navigation) { failed = true; } const duplicateResults = await mapWithConcurrency(trustedStatusComments.slice(1), REVIEW_UPDATE_CONCURRENCY, async (duplicate) => { + const messages = (0, publication_message_catalog_1.resolveStaticPublicationCatalog)(input.target.locale).catalog; await input.ports.comments.updateComment(input.target.pullRequestNumber, duplicate.id, [ - '## 🤖 Bugbot status moved', + (0, publication_identity_policy_1.buildDuplicateMarker)(canonical?.id ?? duplicate.id), + '', + messages.supersededStatus, '', - `This duplicate status card is no longer current. [Use the canonical PR status](${navigation.pullRequestUrl}).`, + `[${messages.viewCurrentStatus}](${navigation.pullRequestUrl}).`, ].join('\n'), { commitSha: input.snapshot.verifiedHeadSha }); }); if (duplicateResults.includes('rejected')) @@ -4507,6 +4635,36 @@ function countActionableBugbotFindings(counts) { } +/***/ }), + +/***/ 5793: +/***/ ((__unused_webpack_module, exports) => { + + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.PUBLICATION_TOPICS = void 0; +exports.publicationIdentityEquals = publicationIdentityEquals; +exports.publicationTargetToken = publicationTargetToken; +exports.PUBLICATION_TOPICS = [ + 'plan', + 'progress', + 'branch-sync', + 'bugbot', + 'release', + 'inactivity', + 'access-policy', +]; +function publicationIdentityEquals(left, right) { + return left.topic === right.topic + && left.target.kind === right.target.kind + && left.target.number === right.target.number + && left.key === right.key; +} +function publicationTargetToken(target) { + return `${target.kind === 'pull-request' ? 'pr' : 'issue'}:${target.number}`; +} + + /***/ }), /***/ 4403: @@ -4522,6 +4680,91 @@ function githubUsersMatch(left, right) { } +/***/ }), + +/***/ 5386: +/***/ ((__unused_webpack_module, exports) => { + + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.InvalidLocaleTagError = exports.MAX_LOCALE_TAG_LENGTH = exports.DEFAULT_REPOSITORY_LOCALE = void 0; +exports.canonicalizeLocaleTag = canonicalizeLocaleTag; +exports.resolveLocaleProfile = resolveLocaleProfile; +exports.localeForScope = localeForScope; +exports.localeLanguagesMatch = localeLanguagesMatch; +exports.baseLanguage = baseLanguage; +exports.DEFAULT_REPOSITORY_LOCALE = 'en-US'; +exports.MAX_LOCALE_TAG_LENGTH = 255; +class InvalidLocaleTagError extends Error { + constructor(input) { + super(`Invalid locale tag: ${JSON.stringify(input)}.`); + this.name = 'InvalidLocaleTagError'; + this.input = input; + } +} +exports.InvalidLocaleTagError = InvalidLocaleTagError; +/** + * Canonicalizes one BCP-47 locale. Underscores are accepted for the documented + * migration window, but every value leaving this boundary uses hyphens. + */ +function canonicalizeLocaleTag(value) { + if (typeof value !== 'string') + throw new InvalidLocaleTagError(String(value)); + const normalized = value.trim().replace(/_/gu, '-'); + if (!normalized || normalized.length > exports.MAX_LOCALE_TAG_LENGTH) { + throw new InvalidLocaleTagError(value); + } + if (/^x(?:-|$)/iu.test(normalized) || /^und(?:-|$)/iu.test(normalized)) { + throw new InvalidLocaleTagError(value); + } + try { + const [canonical] = Intl.getCanonicalLocales(normalized); + if (!canonical) + throw new InvalidLocaleTagError(value); + return canonical; + } + catch (error) { + if (error instanceof InvalidLocaleTagError) + throw error; + throw new InvalidLocaleTagError(value); + } +} +function resolveLocaleProfile(repositoryLocale, issueLocale = '', pullRequestLocale = '') { + const repository = canonicalizeLocaleTag(typeof repositoryLocale === 'string' && repositoryLocale.trim() + ? repositoryLocale + : exports.DEFAULT_REPOSITORY_LOCALE); + const issueOverride = optionalLocale(issueLocale); + const pullRequestOverride = optionalLocale(pullRequestLocale); + return Object.freeze({ + repository, + issue: issueOverride ?? repository, + pullRequest: pullRequestOverride ?? repository, + ...(issueOverride ? { issueOverride } : {}), + ...(pullRequestOverride ? { pullRequestOverride } : {}), + }); +} +function localeForScope(profile, scope) { + if (scope === 'issue') + return profile.issue; + if (scope === 'pull-request') + return profile.pullRequest; + return profile.repository; +} +function localeLanguagesMatch(left, right) { + return baseLanguage(canonicalizeLocaleTag(left)) === baseLanguage(canonicalizeLocaleTag(right)); +} +function baseLanguage(locale) { + return new Intl.Locale(canonicalizeLocaleTag(locale)).language.toLowerCase(); +} +function optionalLocale(value) { + if (value == null || value === '') + return undefined; + if (typeof value !== 'string') + throw new InvalidLocaleTagError(String(value)); + return value.trim() ? canonicalizeLocaleTag(value) : undefined; +} + + /***/ }), /***/ 9879: @@ -5656,28 +5899,10 @@ function format(value) { /***/ ((__unused_webpack_module, exports) => { -/** - * Watermark appended to comments (issues and PRs) to attribute Copilot. - * Bugbot comments include commit link and note about auto-update on new commits. - */ Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.COPILOT_MARKETPLACE_URL = void 0; -exports.getCommentWatermark = getCommentWatermark; exports.stripTrailingCommentWatermarks = stripTrailingCommentWatermarks; -exports.COPILOT_MARKETPLACE_URL = 'https://github.com/marketplace/actions/copilot-github-with-super-powers'; -const DEFAULT_WATERMARK = `Made with ❤️ by [vypdev/copilot](${exports.COPILOT_MARKETPLACE_URL})`; -function commitUrl(owner, repo, sha) { - return `https://github.com/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commit/${sha}`; -} -function getCommentWatermark(options) { - if (options?.commitSha && options?.owner && options?.repo) { - const url = commitUrl(options.owner, options.repo, options.commitSha); - return `Written by [vypdev/copilot](${exports.COPILOT_MARKETPLACE_URL}) for commit [${options.commitSha}](${url}). This will update automatically on new commits.`; - } - return DEFAULT_WATERMARK; -} const TRAILING_COMMENT_WATERMARK = /\s*(?:Made with ❤️ by|Written by) \[vypdev\/copilot\]\(https:\/\/github\.com\/marketplace\/actions\/copilot-github-with-super-powers\)[^<]*<\/sup>\s*$/u; -/** Removes all trailing Copilot watermarks before a read-modify-write update. */ +/** Removes legacy trailing Copilot watermarks before a read-modify-write update. */ function stripTrailingCommentWatermarks(comment) { let stripped = comment; while (TRAILING_COMMENT_WATERMARK.test(stripped)) { diff --git a/build/cli/index.js b/build/cli/index.js index d55b68034..a0ba5f074 100755 --- a/build/cli/index.js +++ b/build/cli/index.js @@ -3816,11711 +3816,6 @@ module.exports = ({onlyFirst = false} = {}) => { }; -/***/ }), - -/***/ 78043: -/***/ ((module, exports) => { - -// Chance.js 1.1.12 -// https://chancejs.com -// (c) 2013 Victor Quinn -// Chance may be freely distributed or modified under the MIT license. - -(function () { - - // Constants - var MAX_INT = 9007199254740992; - var MIN_INT = -MAX_INT; - var NUMBERS = '0123456789'; - var CHARS_LOWER = 'abcdefghijklmnopqrstuvwxyz'; - var CHARS_UPPER = CHARS_LOWER.toUpperCase(); - var HEX_POOL = NUMBERS + "abcdef"; - - // Errors - function UnsupportedError(message) { - this.name = 'UnsupportedError'; - this.message = message || 'This feature is not supported on this platform'; - } - - UnsupportedError.prototype = new Error(); - UnsupportedError.prototype.constructor = UnsupportedError; - - // Cached array helpers - var slice = Array.prototype.slice; - - // Constructor - function Chance (seed) { - if (!(this instanceof Chance)) { - if (!seed) { seed = null; } // handle other non-truthy seeds, as described in issue #322 - return seed === null ? new Chance() : new Chance(seed); - } - - // if user has provided a function, use that as the generator - if (typeof seed === 'function') { - this.random = seed; - return this; - } - - if (arguments.length) { - // set a starting value of zero so we can add to it - this.seed = 0; - } - - // otherwise, leave this.seed blank so that MT will receive a blank - - for (var i = 0; i < arguments.length; i++) { - var seedling = 0; - if (Object.prototype.toString.call(arguments[i]) === '[object String]') { - for (var j = 0; j < arguments[i].length; j++) { - // create a numeric hash for each argument, add to seedling - var hash = 0; - for (var k = 0; k < arguments[i].length; k++) { - hash = arguments[i].charCodeAt(k) + (hash << 6) + (hash << 16) - hash; - } - seedling += hash; - } - } else { - seedling = arguments[i]; - } - this.seed += (arguments.length - i) * seedling; - } - - // If no generator function was provided, use our MT - this.mt = this.mersenne_twister(this.seed); - this.bimd5 = this.blueimp_md5(); - this.random = function () { - return this.mt.random(this.seed); - }; - - return this; - } - - Chance.prototype.VERSION = "1.1.13"; - - // Random helper functions - function initOptions(options, defaults) { - options = options || {}; - - if (defaults) { - for (var i in defaults) { - if (typeof options[i] === 'undefined') { - options[i] = defaults[i]; - } - } - } - - return options; - } - - function range(size) { - return Array.apply(null, Array(size)).map(function (_, i) {return i;}); - } - - function testRange(test, errorMessage) { - if (test) { - throw new RangeError(errorMessage); - } - } - - /** - * Encode the input string with Base64. - */ - var base64 = function() { - throw new Error('No Base64 encoder available.'); - }; - - // Select proper Base64 encoder. - (function determineBase64Encoder() { - if (typeof btoa === 'function') { - base64 = btoa; - } else if (typeof Buffer === 'function') { - base64 = function(input) { - return new Buffer(input).toString('base64'); - }; - } - })(); - - // -- Basics -- - - /** - * Return a random bool, either true or false - * - * @param {Object} [options={ likelihood: 50 }] alter the likelihood of - * receiving a true or false value back. - * @throws {RangeError} if the likelihood is out of bounds - * @returns {Bool} either true or false - */ - Chance.prototype.bool = function (options) { - // likelihood of success (true) - options = initOptions(options, {likelihood : 50}); - - // Note, we could get some minor perf optimizations by checking range - // prior to initializing defaults, but that makes code a bit messier - // and the check more complicated as we have to check existence of - // the object then existence of the key before checking constraints. - // Since the options initialization should be minor computationally, - // decision made for code cleanliness intentionally. This is mentioned - // here as it's the first occurrence, will not be mentioned again. - testRange( - options.likelihood < 0 || options.likelihood > 100, - "Chance: Likelihood accepts values from 0 to 100." - ); - - return this.random() * 100 < options.likelihood; - }; - - Chance.prototype.falsy = function (options) { - // return a random falsy value - options = initOptions(options, {pool: [false, null, 0, NaN, '', undefined]}) - var pool = options.pool, - index = this.integer({min: 0, max: pool.length - 1}), - value = pool[index]; - - return value; - } - - Chance.prototype.animal = function (options){ - //returns a random animal - options = initOptions(options); - - if(typeof options.type !== 'undefined'){ - //if user does not put in a valid animal type, user will get an error - testRange( - !this.get("animals")[options.type.toLowerCase()], - "Please pick from desert, ocean, grassland, forest, zoo, pets, farm." - ); - //if user does put in valid animal type, will return a random animal of that type - return this.pick(this.get("animals")[options.type.toLowerCase()]); - } - //if user does not put in any animal type, will return a random animal regardless - var animalTypeArray = ["desert","forest","ocean","zoo","farm","pet","grassland"]; - return this.pick(this.get("animals")[this.pick(animalTypeArray)]); - }; - - /** - * Return a random character. - * - * @param {Object} [options={}] can specify a character pool or alpha, - * numeric, symbols and casing (lower or upper) - * @returns {String} a single random character - */ - Chance.prototype.character = function (options) { - options = initOptions(options); - - var symbols = "!@#$%^&*()[]", - letters, pool; - - if (options.casing === 'lower') { - letters = CHARS_LOWER; - } else if (options.casing === 'upper') { - letters = CHARS_UPPER; - } else { - letters = CHARS_LOWER + CHARS_UPPER; - } - - if (options.pool) { - pool = options.pool; - } else { - pool = ''; - if (options.alpha) { - pool += letters; - } - if (options.numeric) { - pool += NUMBERS; - } - if (options.symbols) { - pool += symbols; - } - if (!pool) { - pool = letters + NUMBERS + symbols; - } - } - - return pool.charAt(this.natural({max: (pool.length - 1)})); - }; - - // Note, wanted to use "float" or "double" but those are both JS reserved words. - - // Note, fixed means N OR LESS digits after the decimal. This because - // It could be 14.9000 but in JavaScript, when this is cast as a number, - // the trailing zeroes are dropped. Left to the consumer if trailing zeroes are - // needed - /** - * Return a random floating point number - * - * @param {Object} [options={}] can specify a fixed precision, min, max - * @returns {Number} a single floating point number - * @throws {RangeError} Can only specify fixed or precision, not both. Also - * min cannot be greater than max - */ - Chance.prototype.floating = function (options) { - options = initOptions(options, {fixed : 4}); - testRange( - options.fixed && options.precision, - "Chance: Cannot specify both fixed and precision." - ); - - var num; - var fixed = Math.pow(10, options.fixed); - - var max = MAX_INT / fixed; - var min = -max; - - testRange( - options.min && options.fixed && options.min < min, - "Chance: Min specified is out of range with fixed. Min should be, at least, " + min - ); - testRange( - options.max && options.fixed && options.max > max, - "Chance: Max specified is out of range with fixed. Max should be, at most, " + max - ); - - options = initOptions(options, { min : min, max : max }); - - // Todo - Make this work! - // options.precision = (typeof options.precision !== "undefined") ? options.precision : false; - - num = this.integer({min: options.min * fixed, max: options.max * fixed}); - var num_fixed = (num / fixed).toFixed(options.fixed); - - return parseFloat(num_fixed); - }; - - /** - * Return a random integer - * - * NOTE the max and min are INCLUDED in the range. So: - * chance.integer({min: 1, max: 3}); - * would return either 1, 2, or 3. - * - * @param {Object} [options={}] can specify a min and/or max - * @returns {Number} a single random integer number - * @throws {RangeError} min cannot be greater than max - */ - Chance.prototype.integer = function (options) { - // 9007199254740992 (2^53) is the max integer number in JavaScript - // See: http://vq.io/132sa2j - options = initOptions(options, {min: MIN_INT, max: MAX_INT}); - testRange(options.min > options.max, "Chance: Min cannot be greater than Max."); - - return Math.floor(this.random() * (options.max - options.min + 1) + options.min); - }; - - /** - * Return a random natural - * - * NOTE the max and min are INCLUDED in the range. So: - * chance.natural({min: 1, max: 3}); - * would return either 1, 2, or 3. - * - * @param {Object} [options={}] can specify a min and/or max or a numerals count. - * @returns {Number} a single random integer number - * @throws {RangeError} min cannot be greater than max - */ - Chance.prototype.natural = function (options) { - options = initOptions(options, {min: 0, max: MAX_INT}); - if (typeof options.numerals === 'number'){ - testRange(options.numerals < 1, "Chance: Numerals cannot be less than one."); - options.min = Math.pow(10, options.numerals - 1); - options.max = Math.pow(10, options.numerals) - 1; - } - testRange(options.min < 0, "Chance: Min cannot be less than zero."); - - if (options.exclude) { - testRange(!Array.isArray(options.exclude), "Chance: exclude must be an array.") - - for (var exclusionIndex in options.exclude) { - testRange(!Number.isInteger(options.exclude[exclusionIndex]), "Chance: exclude must be numbers.") - } - - var random = options.min + this.natural({max: options.max - options.min - options.exclude.length}) - var sortedExclusions = options.exclude.sort((a, b) => a - b); - for (var sortedExclusionIndex in sortedExclusions) { - if (random < sortedExclusions[sortedExclusionIndex]) { - break - } - random++ - } - return random - } - return this.integer(options); - }; - - /** - * Return a random prime number - * - * NOTE the max and min are INCLUDED in the range. - * - * @param {Object} [options={}] can specify a min and/or max - * @returns {Number} a single random prime number - * @throws {RangeError} min cannot be greater than max nor negative - */ - Chance.prototype.prime = function (options) { - options = initOptions(options, {min: 0, max: 10000}); - testRange(options.min < 0, "Chance: Min cannot be less than zero."); - testRange(options.min > options.max, "Chance: Min cannot be greater than Max."); - - var lastPrime = data.primes[data.primes.length - 1]; - if (options.max > lastPrime) { - for (var i = lastPrime + 2; i <= options.max; ++i) { - if (this.is_prime(i)) { - data.primes.push(i); - } - } - } - var targetPrimes = data.primes.filter(function (prime) { - return prime >= options.min && prime <= options.max; - }); - return this.pick(targetPrimes); - }; - - /** - * Determine whether a given number is prime or not. - */ - Chance.prototype.is_prime = function (n) { - if (n % 1 || n < 2) { - return false; - } - if (n % 2 === 0) { - return n === 2; - } - if (n % 3 === 0) { - return n === 3; - } - var m = Math.sqrt(n); - for (var i = 5; i <= m; i += 6) { - if (n % i === 0 || n % (i + 2) === 0) { - return false; - } - } - return true; - }; - - /** - * Return a random hex number as string - * - * NOTE the max and min are INCLUDED in the range. So: - * chance.hex({min: '9', max: 'B'}); - * would return either '9', 'A' or 'B'. - * - * @param {Object} [options={}] can specify a min and/or max and/or casing - * @returns {String} a single random string hex number - * @throws {RangeError} min cannot be greater than max - */ - Chance.prototype.hex = function (options) { - options = initOptions(options, {min: 0, max: MAX_INT, casing: 'lower'}); - testRange(options.min < 0, "Chance: Min cannot be less than zero."); - var integer = this.natural({min: options.min, max: options.max}); - if (options.casing === 'upper') { - return integer.toString(16).toUpperCase(); - } - return integer.toString(16); - }; - - Chance.prototype.letter = function(options) { - options = initOptions(options, {casing: 'lower'}); - var pool = "abcdefghijklmnopqrstuvwxyz"; - var letter = this.character({pool: pool}); - if (options.casing === 'upper') { - letter = letter.toUpperCase(); - } - return letter; - } - - /** - * Return a random string - * - * @param {Object} [options={}] can specify a length or min and max - * @returns {String} a string of random length - * @throws {RangeError} length cannot be less than zero - */ - Chance.prototype.string = function (options) { - options = initOptions(options, { min: 5, max: 20 }); - - if (options.length !== 0 && !options.length) { - options.length = this.natural({ min: options.min, max: options.max }) - } - - testRange(options.length < 0, "Chance: Length cannot be less than zero."); - var length = options.length, - text = this.n(this.character, length, options); - - return text.join(""); - }; - - function CopyToken(c) { - this.c = c - } - - CopyToken.prototype = { - substitute: function () { - return this.c - } - } - - function EscapeToken(c) { - this.c = c - } - - EscapeToken.prototype = { - substitute: function () { - if (!/[{}\\]/.test(this.c)) { - throw new Error('Invalid escape sequence: "\\' + this.c + '".') - } - return this.c - } - } - - function ReplaceToken(c) { - this.c = c - } - - ReplaceToken.prototype = { - replacers: { - '#': function (chance) { return chance.character({ pool: NUMBERS }) }, - 'A': function (chance) { return chance.character({ pool: CHARS_UPPER }) }, - 'a': function (chance) { return chance.character({ pool: CHARS_LOWER }) }, - }, - - substitute: function (chance) { - var replacer = this.replacers[this.c] - if (!replacer) { - throw new Error('Invalid replacement character: "' + this.c + '".') - } - return replacer(chance) - } - } - - function parseTemplate(template) { - var tokens = [] - var mode = 'identity' - for (var i = 0; i MAX_DUPLICATES) { - throw new RangeError("Chance: num is likely too large for sample set"); - } - } - return arr; - }; - - /** - * Gives an array of n random terms - * - * @param {Function} fn the function that generates something random - * @param {Number} n number of terms to generate - * @returns {Array} an array of length `n` with items generated by `fn` - * - * There can be more parameters after these. All additional parameters are provided to the given function - */ - Chance.prototype.n = function(fn, n) { - testRange( - typeof fn !== "function", - "Chance: The first argument must be a function." - ); - - if (typeof n === 'undefined') { - n = 1; - } - var i = n, arr = [], params = slice.call(arguments, 2); - - // Providing a negative count should result in a noop. - i = Math.max( 0, i ); - - for (null; i--; null) { - arr.push(fn.apply(this, params)); - } - - return arr; - }; - - // H/T to SO for this one: http://vq.io/OtUrZ5 - Chance.prototype.pad = function (number, width, pad) { - // Default pad to 0 if none provided - pad = pad || '0'; - // Convert number to a string - number = number + ''; - return number.length >= width ? number : new Array(width - number.length + 1).join(pad) + number; - }; - - // DEPRECATED on 2015-10-01 - Chance.prototype.pick = function (arr, count) { - if (arr.length === 0) { - throw new RangeError("Chance: Cannot pick() from an empty array"); - } - if (!count || count === 1) { - return arr[this.natural({max: arr.length - 1})]; - } else { - return this.shuffle(arr).slice(0, count); - } - }; - - // Given an array, returns a single random element - Chance.prototype.pickone = function (arr) { - if (arr.length === 0) { - throw new RangeError("Chance: Cannot pickone() from an empty array"); - } - return arr[this.natural({max: arr.length - 1})]; - }; - - // Given an array, returns a random set with 'count' elements - Chance.prototype.pickset = function (arr, count) { - if (count === 0) { - return []; - } - if (arr.length === 0) { - throw new RangeError("Chance: Cannot pickset() from an empty array"); - } - if (count < 0) { - throw new RangeError("Chance: Count must be a positive number"); - } - if (!count || count === 1) { - return [ this.pickone(arr) ]; - } else { - var array = arr.slice(0); - var end = array.length; - - return this.n(function () { - var index = this.natural({max: --end}); - var value = array[index]; - array[index] = array[end]; - return value; - }, Math.min(end, count)); - } - }; - - Chance.prototype.shuffle = function (arr) { - var new_array = [], - j = 0, - length = Number(arr.length), - source_indexes = range(length), - last_source_index = length - 1, - selected_source_index; - - for (var i = 0; i < length; i++) { - // Pick a random index from the array - selected_source_index = this.natural({max: last_source_index}); - j = source_indexes[selected_source_index]; - - // Add it to the new array - new_array[i] = arr[j]; - - // Mark the source index as used - source_indexes[selected_source_index] = source_indexes[last_source_index]; - last_source_index -= 1; - } - - return new_array; - }; - - // Returns a single item from an array with relative weighting of odds - Chance.prototype.weighted = function (arr, weights, trim) { - if (arr.length !== weights.length) { - throw new RangeError("Chance: Length of array and weights must match"); - } - - // scan weights array and sum valid entries - var sum = 0; - var val; - for (var weightIndex = 0; weightIndex < weights.length; ++weightIndex) { - val = weights[weightIndex]; - if (isNaN(val)) { - throw new RangeError("Chance: All weights must be numbers"); - } - - if (val > 0) { - sum += val; - } - } - - if (sum === 0) { - throw new RangeError("Chance: No valid entries in array weights"); - } - - // select a value within range - var selected = this.random() * sum; - - // find array entry corresponding to selected value - var total = 0; - var lastGoodIdx = -1; - var chosenIdx; - for (weightIndex = 0; weightIndex < weights.length; ++weightIndex) { - val = weights[weightIndex]; - total += val; - if (val > 0) { - if (selected <= total) { - chosenIdx = weightIndex; - break; - } - lastGoodIdx = weightIndex; - } - - // handle any possible rounding error comparison to ensure something is picked - if (weightIndex === (weights.length - 1)) { - chosenIdx = lastGoodIdx; - } - } - - var chosen = arr[chosenIdx]; - trim = (typeof trim === 'undefined') ? false : trim; - if (trim) { - arr.splice(chosenIdx, 1); - weights.splice(chosenIdx, 1); - } - - return chosen; - }; - - // -- End Helpers -- - - // -- Text -- - - Chance.prototype.paragraph = function (options) { - options = initOptions(options); - - var sentences = options.sentences || this.natural({min: 3, max: 7}), - sentence_array = this.n(this.sentence, sentences), - separator = options.linebreak === true ? '\n' : ' '; - - return sentence_array.join(separator); - }; - - // Could get smarter about this than generating random words and - // chaining them together. Such as: http://vq.io/1a5ceOh - Chance.prototype.sentence = function (options) { - options = initOptions(options); - - var words = options.words || this.natural({min: 12, max: 18}), - punctuation = options.punctuation, - text, word_array = this.n(this.word, words); - - text = word_array.join(' '); - - // Capitalize first letter of sentence - text = this.capitalize(text); - - // Make sure punctuation has a usable value - if (punctuation !== false && !/^[.?;!:]$/.test(punctuation)) { - punctuation = '.'; - } - - // Add punctuation mark - if (punctuation) { - text += punctuation; - } - - return text; - }; - - Chance.prototype.syllable = function (options) { - options = initOptions(options); - - var length = options.length || this.natural({min: 2, max: 3}), - consonants = 'bcdfghjklmnprstvwz', // consonants except hard to speak ones - vowels = 'aeiou', // vowels - all = consonants + vowels, // all - text = '', - chr; - - // I'm sure there's a more elegant way to do this, but this works - // decently well. - for (var i = 0; i < length; i++) { - if (i === 0) { - // First character can be anything - chr = this.character({pool: all}); - } else if (consonants.indexOf(chr) === -1) { - // Last character was a vowel, now we want a consonant - chr = this.character({pool: consonants}); - } else { - // Last character was a consonant, now we want a vowel - chr = this.character({pool: vowels}); - } - - text += chr; - } - - if (options.capitalize) { - text = this.capitalize(text); - } - - return text; - }; - - Chance.prototype.word = function (options) { - options = initOptions(options); - - testRange( - options.syllables && options.length, - "Chance: Cannot specify both syllables AND length." - ); - - var syllables = options.syllables || this.natural({min: 1, max: 3}), - text = ''; - - if (options.length) { - // Either bound word by length - do { - text += this.syllable(); - } while (text.length < options.length); - text = text.substring(0, options.length); - } else { - // Or by number of syllables - for (var i = 0; i < syllables; i++) { - text += this.syllable(); - } - } - - if (options.capitalize) { - text = this.capitalize(text); - } - - return text; - }; - - Chance.prototype.emoji = function (options) { - options = initOptions(options, { category: "all", length: 1 }); - - testRange( - options.length < 1 || BigInt(options.length) > BigInt(MAX_INT), - "Chance: length must be between 1 and " + String(MAX_INT) - ); - - var emojis = this.get("emojis"); - - if (options.category === "all") { - options.category = this.pickone(Object.keys(emojis)); - } - - var emojisForCategory = emojis[options.category]; - - testRange( - emojisForCategory === undefined, - "Chance: Unrecognised emoji category: [" + options.category + "]." - ); - - return this.pickset(emojisForCategory, options.length) - .map(function (codePoint) { - return String.fromCodePoint(codePoint); - }).join(""); - }; - - // -- End Text -- - - // -- Person -- - - Chance.prototype.age = function (options) { - options = initOptions(options); - var ageRange; - - switch (options.type) { - case 'child': - ageRange = {min: 0, max: 12}; - break; - case 'teen': - ageRange = {min: 13, max: 19}; - break; - case 'adult': - ageRange = {min: 18, max: 65}; - break; - case 'senior': - ageRange = {min: 65, max: 100}; - break; - case 'all': - ageRange = {min: 0, max: 100}; - break; - default: - ageRange = {min: 18, max: 65}; - break; - } - - return this.natural(ageRange); - }; - - Chance.prototype.birthday = function (options) { - var age = this.age(options); - var now = new Date() - var currentYear = now.getFullYear(); - - if (options && options.type) { - var min = new Date(); - var max = new Date(); - min.setFullYear(currentYear - age - 1); - max.setFullYear(currentYear - age); - - options = initOptions(options, { - min: min, - max: max - }); - } else if (options && ((options.minAge !== undefined) || (options.maxAge !== undefined))) { - testRange(options.minAge < 0, "Chance: MinAge cannot be less than zero."); - testRange(options.minAge > options.maxAge, "Chance: MinAge cannot be greater than MaxAge."); - - var minAge = options.minAge !== undefined ? options.minAge : 0; - var maxAge = options.maxAge !== undefined ? options.maxAge : 100; - - var minDate = new Date(currentYear - maxAge - 1, now.getMonth(), now.getDate()); - var maxDate = new Date(currentYear - minAge, now.getMonth(), now.getDate()); - - minDate.setDate(minDate.getDate() +1); - - maxDate.setDate(maxDate.getDate() +1); - maxDate.setMilliseconds(maxDate.getMilliseconds() -1); - - options = initOptions(options, { - min: minDate, - max: maxDate - }); - } else { - options = initOptions(options, { - year: currentYear - age - }); - } - - return this.date(options); - }; - - // CPF; ID to identify taxpayers in Brazil - Chance.prototype.cpf = function (options) { - options = initOptions(options, { - formatted: true - }); - - var n = this.n(this.natural, 9, { max: 9 }); - var d1 = n[8]*2+n[7]*3+n[6]*4+n[5]*5+n[4]*6+n[3]*7+n[2]*8+n[1]*9+n[0]*10; - d1 = 11 - (d1 % 11); - if (d1>=10) { - d1 = 0; - } - var d2 = d1*2+n[8]*3+n[7]*4+n[6]*5+n[5]*6+n[4]*7+n[3]*8+n[2]*9+n[1]*10+n[0]*11; - d2 = 11 - (d2 % 11); - if (d2>=10) { - d2 = 0; - } - var cpf = ''+n[0]+n[1]+n[2]+'.'+n[3]+n[4]+n[5]+'.'+n[6]+n[7]+n[8]+'-'+d1+d2; - return options.formatted ? cpf : cpf.replace(/\D/g,''); - }; - - // CNPJ: ID to identify companies in Brazil - Chance.prototype.cnpj = function (options) { - options = initOptions(options, { - formatted: true - }); - - var n = this.n(this.natural, 12, { max: 12 }); - var d1 = n[11]*2+n[10]*3+n[9]*4+n[8]*5+n[7]*6+n[6]*7+n[5]*8+n[4]*9+n[3]*2+n[2]*3+n[1]*4+n[0]*5; - d1 = 11 - (d1 % 11); - if (d1<2) { - d1 = 0; - } - var d2 = d1*2+n[11]*3+n[10]*4+n[9]*5+n[8]*6+n[7]*7+n[6]*8+n[5]*9+n[4]*2+n[3]*3+n[2]*4+n[1]*5+n[0]*6; - d2 = 11 - (d2 % 11); - if (d2<2) { - d2 = 0; - } - var cnpj = ''+n[0]+n[1]+'.'+n[2]+n[3]+n[4]+'.'+n[5]+n[6]+n[7]+'/'+n[8]+n[9]+n[10]+n[11]+'-'+d1+d2; - return options.formatted ? cnpj : cnpj.replace(/\D/g,''); - }; - - Chance.prototype.first = function (options) { - options = initOptions(options, {gender: this.gender(), nationality: 'en'}); - return this.pick(this.get("firstNames")[options.gender.toLowerCase()][options.nationality.toLowerCase()]); - }; - - Chance.prototype.profession = function (options) { - options = initOptions(options); - if(options.rank){ - return this.pick(['Apprentice ', 'Junior ', 'Senior ', 'Lead ']) + this.pick(this.get("profession")); - } else{ - return this.pick(this.get("profession")); - } - }; - - Chance.prototype.company = function (){ - return this.pick(this.get("company")); - }; - - Chance.prototype.gender = function (options) { - options = initOptions(options, {extraGenders: []}); - return this.pick(['Male', 'Female'].concat(options.extraGenders)); - }; - - Chance.prototype.last = function (options) { - options = initOptions(options, {nationality: '*'}); - if (options.nationality === "*") { - var allLastNames = [] - var lastNames = this.get("lastNames") - Object.keys(lastNames).forEach(function(key){ - allLastNames = allLastNames.concat(lastNames[key]) - }) - return this.pick(allLastNames) - } - else { - return this.pick(this.get("lastNames")[options.nationality.toLowerCase()]); - } - - }; - - Chance.prototype.israelId=function(){ - var x=this.string({pool: '0123456789',length:8}); - var y=0; - for (var i=0;i hex - * -> rgb - * -> rgba - * -> 0x - * -> named color - * - * #Examples: - * =============================================== - * * Geerate random hex color - * chance.color() => '#79c157' / 'rgb(110,52,164)' / '0x67ae0b' / '#e2e2e2' / '#29CFA7' - * - * * Generate Hex based color value - * chance.color({format: 'hex'}) => '#d67118' - * - * * Generate simple rgb value - * chance.color({format: 'rgb'}) => 'rgb(110,52,164)' - * - * * Generate Ox based color value - * chance.color({format: '0x'}) => '0x67ae0b' - * - * * Generate graiscale based value - * chance.color({grayscale: true}) => '#e2e2e2' - * - * * Return valide color name - * chance.color({format: 'name'}) => 'red' - * - * * Make color uppercase - * chance.color({casing: 'upper'}) => '#29CFA7' - * - * * Min Max values for RGBA - * var light_red = chance.color({format: 'hex', min_red: 200, max_red: 255, max_green: 0, max_blue: 0, min_alpha: .2, max_alpha: .3}); - * - * @param [object] options - * @return [string] color value - */ - Chance.prototype.color = function (options) { - function gray(value, delimiter) { - return [value, value, value].join(delimiter || ''); - } - - function rgb(hasAlpha) { - var rgbValue = (hasAlpha) ? 'rgba' : 'rgb'; - var alphaChannel = (hasAlpha) ? (',' + this.floating({min:min_alpha, max:max_alpha})) : ""; - var colorValue = (isGrayscale) ? (gray(this.natural({min: min_rgb, max: max_rgb}), ',')) : (this.natural({min: min_green, max: max_green}) + ',' + this.natural({min: min_blue, max: max_blue}) + ',' + this.natural({max: 255})); - return rgbValue + '(' + colorValue + alphaChannel + ')'; - } - - function hex(start, end, withHash) { - var symbol = (withHash) ? "#" : ""; - var hexstring = ""; - - if (isGrayscale) { - hexstring = gray(this.pad(this.hex({min: min_rgb, max: max_rgb}), 2)); - if (options.format === "shorthex") { - hexstring = gray(this.hex({min: 0, max: 15})); - } - } - else { - if (options.format === "shorthex") { - hexstring = this.pad(this.hex({min: Math.floor(min_red / 16), max: Math.floor(max_red / 16)}), 1) + this.pad(this.hex({min: Math.floor(min_green / 16), max: Math.floor(max_green / 16)}), 1) + this.pad(this.hex({min: Math.floor(min_blue / 16), max: Math.floor(max_blue / 16)}), 1); - } - else if (min_red !== undefined || max_red !== undefined || min_green !== undefined || max_green !== undefined || min_blue !== undefined || max_blue !== undefined) { - hexstring = this.pad(this.hex({min: min_red, max: max_red}), 2) + this.pad(this.hex({min: min_green, max: max_green}), 2) + this.pad(this.hex({min: min_blue, max: max_blue}), 2); - } - else { - hexstring = this.pad(this.hex({min: min_rgb, max: max_rgb}), 2) + this.pad(this.hex({min: min_rgb, max: max_rgb}), 2) + this.pad(this.hex({min: min_rgb, max: max_rgb}), 2); - } - } - - return symbol + hexstring; - } - - options = initOptions(options, { - format: this.pick(['hex', 'shorthex', 'rgb', 'rgba', '0x', 'name']), - grayscale: false, - casing: 'lower', - min: 0, - max: 255, - min_red: undefined, - max_red: undefined, - min_green: undefined, - max_green: undefined, - min_blue: undefined, - max_blue: undefined, - min_alpha: 0, - max_alpha: 1 - }); - - var isGrayscale = options.grayscale; - var min_rgb = options.min; - var max_rgb = options.max; - var min_red = options.min_red; - var max_red = options.max_red; - var min_green = options.min_green; - var max_green = options.max_green; - var min_blue = options.min_blue; - var max_blue = options.max_blue; - var min_alpha = options.min_alpha; - var max_alpha = options.max_alpha; - if (options.min_red === undefined) { min_red = min_rgb; } - if (options.max_red === undefined) { max_red = max_rgb; } - if (options.min_green === undefined) { min_green = min_rgb; } - if (options.max_green === undefined) { max_green = max_rgb; } - if (options.min_blue === undefined) { min_blue = min_rgb; } - if (options.max_blue === undefined) { max_blue = max_rgb; } - if (options.min_alpha === undefined) { min_alpha = 0; } - if (options.max_alpha === undefined) { max_alpha = 1; } - if (isGrayscale && min_rgb === 0 && max_rgb === 255 && min_red !== undefined && max_red !== undefined) { - min_rgb = ((min_red + min_green + min_blue) / 3); - max_rgb = ((max_red + max_green + max_blue) / 3); - } - var colorValue; - - if (options.format === 'hex') { - colorValue = hex.call(this, 2, 6, true); - } - else if (options.format === 'shorthex') { - colorValue = hex.call(this, 1, 3, true); - } - else if (options.format === 'rgb') { - colorValue = rgb.call(this, false); - } - else if (options.format === 'rgba') { - colorValue = rgb.call(this, true); - } - else if (options.format === '0x') { - colorValue = '0x' + hex.call(this, 2, 6); - } - else if(options.format === 'name') { - return this.pick(this.get("colorNames")); - } - else { - throw new RangeError('Invalid format provided. Please provide one of "hex", "shorthex", "rgb", "rgba", "0x" or "name".'); - } - - if (options.casing === 'upper' ) { - colorValue = colorValue.toUpperCase(); - } - - return colorValue; - }; - - Chance.prototype.domain = function (options) { - options = initOptions(options); - return this.word() + '.' + (options.tld || this.tld()); - }; - - Chance.prototype.email = function (options) { - options = initOptions(options); - return this.word({length: options.length}) + '@' + (options.domain || this.domain()); - }; - - /** - * #Description: - * =============================================== - * Generate a random Facebook id, aka fbid. - * - * NOTE: At the moment (Sep 2017), Facebook ids are - * "numeric strings" of length 16. - * However, Facebook Graph API documentation states that - * "it is extremely likely to change over time". - * @see https://developers.facebook.com/docs/graph-api/overview/ - * - * #Examples: - * =============================================== - * chance.fbid() => '1000035231661304' - * - * @return [string] facebook id - */ - Chance.prototype.fbid = function () { - return '10000' + this.string({pool: "1234567890", length: 11}); - }; - - Chance.prototype.google_analytics = function () { - var account = this.pad(this.natural({max: 999999}), 6); - var property = this.pad(this.natural({max: 99}), 2); - - return 'UA-' + account + '-' + property; - }; - - Chance.prototype.hashtag = function () { - return '#' + this.word(); - }; - - Chance.prototype.ip = function () { - // Todo: This could return some reserved IPs. See http://vq.io/137dgYy - // this should probably be updated to account for that rare as it may be - return this.natural({min: 1, max: 254}) + '.' + - this.natural({max: 255}) + '.' + - this.natural({max: 255}) + '.' + - this.natural({min: 1, max: 254}); - }; - - Chance.prototype.ipv6 = function () { - var ip_addr = this.n(this.hash, 8, {length: 4}); - - return ip_addr.join(":"); - }; - - Chance.prototype.klout = function () { - return this.natural({min: 1, max: 99}); - }; - - Chance.prototype.mac = function (options) { - // Todo: This could also be extended to EUI-64 based MACs - // (https://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml#ethernet-numbers-4) - // Todo: This can return some reserved MACs (similar to IP function) - // this should probably be updated to account for that rare as it may be - options = initOptions(options, { delimiter: ':' }); - return this.pad(this.natural({max: 255}).toString(16),2) + options.delimiter + - this.pad(this.natural({max: 255}).toString(16),2) + options.delimiter + - this.pad(this.natural({max: 255}).toString(16),2) + options.delimiter + - this.pad(this.natural({max: 255}).toString(16),2) + options.delimiter + - this.pad(this.natural({max: 255}).toString(16),2) + options.delimiter + - this.pad(this.natural({max: 255}).toString(16),2); - }; - - Chance.prototype.semver = function (options) { - options = initOptions(options, { include_prerelease: true }); - - var range = this.pickone(["^", "~", "<", ">", "<=", ">=", "="]); - if (options.range) { - range = options.range; - } - - var prerelease = ""; - if (options.include_prerelease) { - prerelease = this.weighted(["", "-dev", "-beta", "-alpha"], [50, 10, 5, 1]); - } - return range + this.rpg('3d10').join('.') + prerelease; - }; - - Chance.prototype.tlds = function () { - return ['com', 'org', 'edu', 'gov', 'co.uk', 'net', 'io', 'ac', 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'ao', 'aq', 'ar', 'as', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh', 'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cc', 'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'cr', 'cu', 'cv', 'cw', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'ee', 'eg', 'eh', 'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq', 'ir', 'is', 'it', 'je', 'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', 'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mk', 'ml', 'mm', 'mn', 'mo', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'nc', 'ne', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'pa', 'pe', 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', 'ss', 'st', 'su', 'sv', 'sx', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th', 'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', 'uk', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye', 'yt', 'za', 'zm', 'zw']; - }; - - Chance.prototype.tld = function () { - return this.pick(this.tlds()); - }; - - Chance.prototype.twitter = function () { - return '@' + this.word(); - }; - - Chance.prototype.url = function (options) { - options = initOptions(options, { protocol: "http", domain: this.domain(options), domain_prefix: "", path: this.word(), extensions: []}); - - var extension = options.extensions.length > 0 ? "." + this.pick(options.extensions) : ""; - var domain = options.domain_prefix ? options.domain_prefix + "." + options.domain : options.domain; - - return options.protocol + "://" + domain + "/" + options.path + extension; - }; - - Chance.prototype.port = function() { - return this.integer({min: 0, max: 65535}); - }; - - Chance.prototype.locale = function (options) { - options = initOptions(options); - if (options.region){ - return this.pick(this.get("locale_regions")); - } else { - return this.pick(this.get("locale_languages")); - } - }; - - Chance.prototype.locales = function (options) { - options = initOptions(options); - if (options.region){ - return this.get("locale_regions"); - } else { - return this.get("locale_languages"); - } - }; - - Chance.prototype.loremPicsum = function (options) { - options = initOptions(options, { width: 500, height: 500, greyscale: false, blurred: false }); - - var greyscale = options.greyscale ? 'g/' : ''; - var query = options.blurred ? '/?blur' : '/?random'; - - return 'https://picsum.photos/' + greyscale + options.width + '/' + options.height + query; - } - - // -- End Web -- - - // -- Location -- - - Chance.prototype.address = function (options) { - options = initOptions(options); - return this.natural({min: 5, max: 2000}) + ' ' + this.street(options); - }; - - Chance.prototype.altitude = function (options) { - options = initOptions(options, {fixed: 5, min: 0, max: 8848}); - return this.floating({ - min: options.min, - max: options.max, - fixed: options.fixed - }); - }; - - Chance.prototype.areacode = function (options) { - options = initOptions(options, {parens : true}); - // Don't want area codes to start with 1, or have a 9 as the second digit - var areacode = options.exampleNumber ? - "555" : - this.natural({min: 2, max: 9}).toString() + - this.natural({min: 0, max: 8}).toString() + - this.natural({min: 0, max: 9}).toString(); - - return options.parens ? '(' + areacode + ')' : areacode; - }; - - Chance.prototype.city = function () { - return this.capitalize(this.word({syllables: 3})); - }; - - Chance.prototype.coordinates = function (options) { - return this.latitude(options) + ', ' + this.longitude(options); - }; - - Chance.prototype.countries = function () { - return this.get("countries"); - }; - - Chance.prototype.country = function (options) { - options = initOptions(options); - var country = this.pick(this.countries()); - return options.raw ? country : options.full ? country.name : country.abbreviation; - }; - - Chance.prototype.depth = function (options) { - options = initOptions(options, {fixed: 5, min: -10994, max: 0}); - return this.floating({ - min: options.min, - max: options.max, - fixed: options.fixed - }); - }; - - Chance.prototype.geohash = function (options) { - options = initOptions(options, { length: 7 }); - return this.string({ length: options.length, pool: '0123456789bcdefghjkmnpqrstuvwxyz' }); - }; - - Chance.prototype.geojson = function (options) { - return this.latitude(options) + ', ' + this.longitude(options) + ', ' + this.altitude(options); - }; - - Chance.prototype.latitude = function (options) { - // Constants - Formats - var [DDM, DMS, DD] = ['ddm', 'dms', 'dd']; - - options = initOptions( -options, - options && options.format && [DDM, DMS].includes(options.format.toLowerCase()) ? - {min: 0, max: 89, fixed: 4} : - {fixed: 5, min: -90, max: 90, format: DD} -); - - var format = options.format.toLowerCase(); - - if (format === DDM || format === DMS) { - testRange(options.min < 0 || options.min > 89, "Chance: Min specified is out of range. Should be between 0 - 89"); - testRange(options.max < 0 || options.max > 89, "Chance: Max specified is out of range. Should be between 0 - 89"); - testRange(options.fixed > 4, 'Chance: Fixed specified should be below or equal to 4'); - } - - switch (format) { - case DDM: { - return this.integer({min: options.min, max: options.max}) + '°' + - this.floating({min: 0, max: 59, fixed: options.fixed}); - } - case DMS: { - return this.integer({min: options.min, max: options.max}) + '°' + - this.integer({min: 0, max: 59}) + '’' + - this.floating({min: 0, max: 59, fixed: options.fixed}) + '”'; - } - case DD: - default: { - return this.floating({min: options.min, max: options.max, fixed: options.fixed}); - } - } - }; - - Chance.prototype.longitude = function (options) { - // Constants - Formats - var [DDM, DMS, DD] = ['ddm', 'dms', 'dd']; - - options = initOptions( -options, - options && options.format && [DDM, DMS].includes(options.format.toLowerCase()) ? - {min: 0, max: 179, fixed: 4} : - {fixed: 5, min: -180, max: 180, format: DD} -); - - var format = options.format.toLowerCase(); - - if (format === DDM || format === DMS) { - testRange(options.min < 0 || options.min > 179, "Chance: Min specified is out of range. Should be between 0 - 179"); - testRange(options.max < 0 || options.max > 179, "Chance: Max specified is out of range. Should be between 0 - 179"); - testRange(options.fixed > 4, 'Chance: Fixed specified should be below or equal to 4'); - } - - switch (format) { - case DDM: { - return this.integer({min: options.min, max: options.max}) + '°' + - this.floating({min: 0, max: 59.9999, fixed: options.fixed}) - } - case DMS: { - return this.integer({min: options.min, max: options.max}) + '°' + - this.integer({min: 0, max: 59}) + '’' + - this.floating({min: 0, max: 59.9999, fixed: options.fixed}) + '”'; - } - case DD: - default: { - return this.floating({min: options.min, max: options.max, fixed: options.fixed}); - } - } - }; - - Chance.prototype.phone = function (options) { - var self = this, - numPick, - ukNum = function (parts) { - var section = []; - //fills the section part of the phone number with random numbers. - parts.sections.forEach(function(n) { - section.push(self.string({ pool: '0123456789', length: n})); - }); - return parts.area + section.join(' '); - }; - options = initOptions(options, { - formatted: true, - country: 'us', - mobile: false, - exampleNumber: false, - }); - if (!options.formatted) { - options.parens = false; - } - var phone; - switch (options.country) { - case 'fr': - if (!options.mobile) { - numPick = this.pick([ - // Valid zone and département codes. - '01' + this.pick(['30', '34', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '53', '55', '56', '58', '60', '64', '69', '70', '72', '73', '74', '75', '76', '77', '78', '79', '80', '81', '82', '83']) + self.string({ pool: '0123456789', length: 6}), - '02' + this.pick(['14', '18', '22', '23', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '40', '41', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '56', '57', '61', '62', '69', '72', '76', '77', '78', '85', '90', '96', '97', '98', '99']) + self.string({ pool: '0123456789', length: 6}), - '03' + this.pick(['10', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '39', '44', '45', '51', '52', '54', '55', '57', '58', '59', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '70', '71', '72', '73', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '90']) + self.string({ pool: '0123456789', length: 6}), - '04' + this.pick(['11', '13', '15', '20', '22', '26', '27', '30', '32', '34', '37', '42', '43', '44', '50', '56', '57', '63', '66', '67', '68', '69', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '80', '81', '82', '83', '84', '85', '86', '88', '89', '90', '91', '92', '93', '94', '95', '97', '98']) + self.string({ pool: '0123456789', length: 6}), - '05' + this.pick(['08', '16', '17', '19', '24', '31', '32', '33', '34', '35', '40', '45', '46', '47', '49', '53', '55', '56', '57', '58', '59', '61', '62', '63', '64', '65', '67', '79', '81', '82', '86', '87', '90', '94']) + self.string({ pool: '0123456789', length: 6}), - '09' + self.string({ pool: '0123456789', length: 8}), - ]); - phone = options.formatted ? numPick.match(/../g).join(' ') : numPick; - } else { - numPick = this.pick(['06', '07']) + self.string({ pool: '0123456789', length: 8}); - phone = options.formatted ? numPick.match(/../g).join(' ') : numPick; - } - break; - case 'uk': - if (!options.mobile) { - numPick = this.pick([ - //valid area codes of major cities/counties followed by random numbers in required format. - - { area: '01' + this.character({ pool: '234569' }) + '1 ', sections: [3,4] }, - { area: '020 ' + this.character({ pool: '378' }), sections: [3,4] }, - { area: '023 ' + this.character({ pool: '89' }), sections: [3,4] }, - { area: '024 7', sections: [3,4] }, - { area: '028 ' + this.pick(['25','28','37','71','82','90','92','95']), sections: [2,4] }, - { area: '012' + this.pick(['04','08','54','76','97','98']) + ' ', sections: [6] }, - { area: '013' + this.pick(['63','64','84','86']) + ' ', sections: [6] }, - { area: '014' + this.pick(['04','20','60','61','80','88']) + ' ', sections: [6] }, - { area: '015' + this.pick(['24','27','62','66']) + ' ', sections: [6] }, - { area: '016' + this.pick(['06','29','35','47','59','95']) + ' ', sections: [6] }, - { area: '017' + this.pick(['26','44','50','68']) + ' ', sections: [6] }, - { area: '018' + this.pick(['27','37','84','97']) + ' ', sections: [6] }, - { area: '019' + this.pick(['00','05','35','46','49','63','95']) + ' ', sections: [6] } - ]); - phone = options.formatted ? ukNum(numPick) : ukNum(numPick).replace(' ', '', 'g'); - } else { - numPick = this.pick([ - { area: '07' + this.pick(['4','5','7','8','9']), sections: [2,6] }, - { area: '07624 ', sections: [6] } - ]); - phone = options.formatted ? ukNum(numPick) : ukNum(numPick).replace(' ', ''); - } - break; - case 'za': - if (!options.mobile) { - numPick = this.pick([ - '01' + this.pick(['0', '1', '2', '3', '4', '5', '6', '7', '8']) + self.string({ pool: '0123456789', length: 7}), - '02' + this.pick(['1', '2', '3', '4', '7', '8']) + self.string({ pool: '0123456789', length: 7}), - '03' + this.pick(['1', '2', '3', '5', '6', '9']) + self.string({ pool: '0123456789', length: 7}), - '04' + this.pick(['1', '2', '3', '4', '5','6','7', '8','9']) + self.string({ pool: '0123456789', length: 7}), - '05' + this.pick(['1', '3', '4', '6', '7', '8']) + self.string({ pool: '0123456789', length: 7}), - ]); - phone = options.formatted || numPick; - } else { - numPick = this.pick([ - '060' + this.pick(['3','4','5','6','7','8','9']) + self.string({ pool: '0123456789', length: 6}), - '061' + this.pick(['0','1','2','3','4','5','8']) + self.string({ pool: '0123456789', length: 6}), - '06' + self.string({ pool: '0123456789', length: 7}), - '071' + this.pick(['0','1','2','3','4','5','6','7','8','9']) + self.string({ pool: '0123456789', length: 6}), - '07' + this.pick(['2','3','4','6','7','8','9']) + self.string({ pool: '0123456789', length: 7}), - '08' + this.pick(['0','1','2','3','4','5']) + self.string({ pool: '0123456789', length: 7}), - ]); - phone = options.formatted || numPick; - } - break; - case 'us': - var areacode = this.areacode(options).toString(); - var exchange = this.natural({ min: 2, max: 9 }).toString() + - this.natural({ min: 0, max: 9 }).toString() + - this.natural({ min: 0, max: 9 }).toString(); - var subscriber = this.natural({ min: 1000, max: 9999 }).toString(); // this could be random [0-9]{4} - phone = options.formatted ? areacode + ' ' + exchange + '-' + subscriber : areacode + exchange + subscriber; - break; - case 'br': - var areaCode = this.pick(["11", "12", "13", "14", "15", "16", "17", "18", "19", "21", "22", "24", "27", "28", "31", "32", "33", "34", "35", "37", "38", "41", "42", "43", "44", "45", "46", "47", "48", "49", "51", "53", "54", "55", "61", "62", "63", "64", "65", "66", "67", "68", "69", "71", "73", "74", "75", "77", "79", "81", "82", "83", "84", "85", "86", "87", "88", "89", "91", "92", "93", "94", "95", "96", "97", "98", "99"]); - var prefix; - if (options.mobile) { - // Brasilian official reference (mobile): http://www.anatel.gov.br/setorregulado/plano-de-numeracao-brasileiro?id=330 - prefix = '9' + self.string({ pool: '0123456789', length: 4}); - } else { - // Brasilian official reference: http://www.anatel.gov.br/setorregulado/plano-de-numeracao-brasileiro?id=331 - prefix = this.natural({ min: 2000, max: 5999 }).toString(); - } - var mcdu = self.string({ pool: '0123456789', length: 4}); - phone = options.formatted ? '(' + areaCode + ') ' + prefix + '-' + mcdu : areaCode + prefix + mcdu; - break; - } - return phone; - }; - - Chance.prototype.postal = function () { - // Postal District - var pd = this.character({pool: "XVTSRPNKLMHJGECBA"}); - // Forward Sortation Area (FSA) - var fsa = pd + this.natural({max: 9}) + this.character({alpha: true, casing: "upper"}); - // Local Delivery Unut (LDU) - var ldu = this.natural({max: 9}) + this.character({alpha: true, casing: "upper"}) + this.natural({max: 9}); - - return fsa + " " + ldu; - }; - - Chance.prototype.postcode = function () { - // Area - var area = this.pick(this.get("postcodeAreas")).code; - // District - var district = this.natural({max: 9}); - // Sub-District - var subDistrict = this.bool() ? this.character({alpha: true, casing: "upper"}) : ""; - // Outward Code - var outward = area + district + subDistrict; - // Sector - var sector = this.natural({max: 9}); - // Unit - var unit = this.character({alpha: true, casing: "upper"}) + this.character({alpha: true, casing: "upper"}); - // Inward Code - var inward = sector + unit; - - return outward + " " + inward; - }; - - Chance.prototype.counties = function (options) { - options = initOptions(options, { country: 'uk' }); - return this.get("counties")[options.country.toLowerCase()]; - }; - - Chance.prototype.county = function (options) { - return this.pick(this.counties(options)).name; - }; - - Chance.prototype.provinces = function (options) { - options = initOptions(options, { country: 'ca' }); - return this.get("provinces")[options.country.toLowerCase()]; - }; - - Chance.prototype.province = function (options) { - return (options && options.full) ? - this.pick(this.provinces(options)).name : - this.pick(this.provinces(options)).abbreviation; - }; - - Chance.prototype.state = function (options) { - return (options && options.full) ? - this.pick(this.states(options)).name : - this.pick(this.states(options)).abbreviation; - }; - - Chance.prototype.states = function (options) { - options = initOptions(options, { country: 'us', us_states_and_dc: true } ); - - var states; - - switch (options.country.toLowerCase()) { - case 'us': - var us_states_and_dc = this.get("us_states_and_dc"), - territories = this.get("territories"), - armed_forces = this.get("armed_forces"); - - states = []; - - if (options.us_states_and_dc) { - states = states.concat(us_states_and_dc); - } - if (options.territories) { - states = states.concat(territories); - } - if (options.armed_forces) { - states = states.concat(armed_forces); - } - break; - case 'it': - case 'mx': - states = this.get("country_regions")[options.country.toLowerCase()]; - break; - case 'uk': - states = this.get("counties")[options.country.toLowerCase()]; - break; - } - - return states; - }; - - Chance.prototype.street = function (options) { - options = initOptions(options, { country: 'us', syllables: 2 }); - var street; - - switch (options.country.toLowerCase()) { - case 'us': - street = this.word({ syllables: options.syllables }); - street = this.capitalize(street); - street += ' '; - street += options.short_suffix ? - this.street_suffix(options).abbreviation : - this.street_suffix(options).name; - break; - case 'it': - street = this.word({ syllables: options.syllables }); - street = this.capitalize(street); - street = (options.short_suffix ? - this.street_suffix(options).abbreviation : - this.street_suffix(options).name) + " " + street; - break; - } - return street; - }; - - Chance.prototype.street_suffix = function (options) { - options = initOptions(options, { country: 'us' }); - return this.pick(this.street_suffixes(options)); - }; - - Chance.prototype.street_suffixes = function (options) { - options = initOptions(options, { country: 'us' }); - // These are the most common suffixes. - return this.get("street_suffixes")[options.country.toLowerCase()]; - }; - - // Note: only returning US zip codes, internationalization will be a whole - // other beast to tackle at some point. - Chance.prototype.zip = function (options) { - var zip = this.n(this.natural, 5, {max: 9}); - - if (options && options.plusfour === true) { - zip.push('-'); - zip = zip.concat(this.n(this.natural, 4, {max: 9})); - } - - return zip.join(""); - }; - - // -- End Location -- - - // -- Time - - Chance.prototype.ampm = function () { - return this.bool() ? 'am' : 'pm'; - }; - - Chance.prototype.date = function (options) { - var date_string, date; - - // If interval is specified we ignore preset - if(options && (options.min || options.max)) { - options = initOptions(options, { - american: true, - string: false - }); - var min = typeof options.min !== "undefined" ? options.min.getTime() : 1; - // 100,000,000 days measured relative to midnight at the beginning of 01 January, 1970 UTC. http://es5.github.io/#x15.9.1.1 - var max = typeof options.max !== "undefined" ? options.max.getTime() : 8640000000000000; - - date = new Date(this.integer({min: min, max: max})); - } else { - var m = this.month({raw: true}); - var daysInMonth = m.days; - - if(options && options.month) { - // Mod 12 to allow months outside range of 0-11 (not encouraged, but also not prevented). - daysInMonth = this.get('months')[((options.month % 12) + 12) % 12].days; - } - - options = initOptions(options, { - year: parseInt(this.year(), 10), - // Necessary to subtract 1 because Date() 0-indexes month but not day or year - // for some reason. - month: m.numeric - 1, - day: this.natural({min: 1, max: daysInMonth}), - hour: this.hour({twentyfour: true}), - minute: this.minute(), - second: this.second(), - millisecond: this.millisecond(), - american: true, - string: false - }); - - date = new Date(options.year, options.month, options.day, options.hour, options.minute, options.second, options.millisecond); - } - - if (options.american) { - // Adding 1 to the month is necessary because Date() 0-indexes - // months but not day for some odd reason. - date_string = (date.getMonth() + 1) + '/' + date.getDate() + '/' + date.getFullYear(); - } else { - date_string = date.getDate() + '/' + (date.getMonth() + 1) + '/' + date.getFullYear(); - } - - return options.string ? date_string : date; - }; - - Chance.prototype.hammertime = function (options) { - return this.date(options).getTime(); - }; - - Chance.prototype.hour = function (options) { - options = initOptions(options, { - min: options && options.twentyfour ? 0 : 1, - max: options && options.twentyfour ? 23 : 12 - }); - - testRange(options.min < 0, "Chance: Min cannot be less than 0."); - testRange(options.twentyfour && options.max > 23, "Chance: Max cannot be greater than 23 for twentyfour option."); - testRange(!options.twentyfour && options.max > 12, "Chance: Max cannot be greater than 12."); - testRange(options.min > options.max, "Chance: Min cannot be greater than Max."); - - return this.natural({min: options.min, max: options.max}); - }; - - Chance.prototype.millisecond = function () { - return this.natural({max: 999}); - }; - - Chance.prototype.minute = Chance.prototype.second = function (options) { - options = initOptions(options, {min: 0, max: 59}); - - testRange(options.min < 0, "Chance: Min cannot be less than 0."); - testRange(options.max > 59, "Chance: Max cannot be greater than 59."); - testRange(options.min > options.max, "Chance: Min cannot be greater than Max."); - - return this.natural({min: options.min, max: options.max}); - }; - - Chance.prototype.month = function (options) { - options = initOptions(options, {min: 1, max: 12}); - - testRange(options.min < 1, "Chance: Min cannot be less than 1."); - testRange(options.max > 12, "Chance: Max cannot be greater than 12."); - testRange(options.min > options.max, "Chance: Min cannot be greater than Max."); - - var month = this.pick(this.months().slice(options.min - 1, options.max)); - return options.raw ? month : month.name; - }; - - Chance.prototype.months = function () { - return this.get("months"); - }; - - Chance.prototype.second = function () { - return this.natural({max: 59}); - }; - - Chance.prototype.timestamp = function () { - return this.natural({min: 1, max: parseInt(new Date().getTime() / 1000, 10)}); - }; - - Chance.prototype.weekday = function (options) { - options = initOptions(options, {weekday_only: false}); - var weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]; - if (!options.weekday_only) { - weekdays.push("Saturday"); - weekdays.push("Sunday"); - } - return this.pickone(weekdays); - }; - - Chance.prototype.year = function (options) { - // Default to current year as min if none specified - options = initOptions(options, {min: new Date().getFullYear()}); - - // Default to one century after current year as max if none specified - options.max = (typeof options.max !== "undefined") ? options.max : options.min + 100; - - return this.natural(options).toString(); - }; - - // -- End Time - - // -- Finance -- - - Chance.prototype.cc = function (options) { - options = initOptions(options); - - var type, number, to_generate; - - type = (options.type) ? - this.cc_type({ name: options.type, raw: true }) : - this.cc_type({ raw: true }); - - number = type.prefix.split(""); - to_generate = type.length - type.prefix.length - 1; - - // Generates n - 1 digits - number = number.concat(this.n(this.integer, to_generate, {min: 0, max: 9})); - - // Generates the last digit according to Luhn algorithm - number.push(this.luhn_calculate(number.join(""))); - - return number.join(""); - }; - - Chance.prototype.cc_types = function () { - // http://en.wikipedia.org/wiki/Bank_card_number#Issuer_identification_number_.28IIN.29 - return this.get("cc_types"); - }; - - Chance.prototype.cc_type = function (options) { - options = initOptions(options); - var types = this.cc_types(), - type = null; - - if (options.name) { - for (var i = 0; i < types.length; i++) { - // Accept either name or short_name to specify card type - if (types[i].name === options.name || types[i].short_name === options.name) { - type = types[i]; - break; - } - } - if (type === null) { - throw new RangeError("Chance: Credit card type '" + options.name + "' is not supported"); - } - } else { - type = this.pick(types); - } - - return options.raw ? type : type.name; - }; - - // return all world currency by ISO 4217 - Chance.prototype.currency_types = function () { - return this.get("currency_types"); - }; - - // return random world currency by ISO 4217 - Chance.prototype.currency = function () { - return this.pick(this.currency_types()); - }; - - // return all timezones available - Chance.prototype.timezones = function () { - return this.get("timezones"); - }; - - // return random timezone - Chance.prototype.timezone = function () { - return this.pick(this.timezones()); - }; - - //Return random correct currency exchange pair (e.g. EUR/USD) or array of currency code - Chance.prototype.currency_pair = function (returnAsString) { - var currencies = this.unique(this.currency, 2, { - comparator: function(arr, val) { - - return arr.reduce(function(acc, item) { - // If a match has been found, short circuit check and just return - return acc || (item.code === val.code); - }, false); - } - }); - - if (returnAsString) { - return currencies[0].code + '/' + currencies[1].code; - } else { - return currencies; - } - }; - - Chance.prototype.dollar = function (options) { - // By default, a somewhat more sane max for dollar than all available numbers - options = initOptions(options, {max : 10000, min : 0}); - - var dollar = this.floating({min: options.min, max: options.max, fixed: 2}).toString(), - cents = dollar.split('.')[1]; - - if (cents === undefined) { - dollar += '.00'; - } else if (cents.length < 2) { - dollar = dollar + '0'; - } - - if (dollar < 0) { - return '-$' + dollar.replace('-', ''); - } else { - return '$' + dollar; - } - }; - - Chance.prototype.euro = function (options) { - return Number(this.dollar(options).replace("$", "")).toLocaleString() + "€"; - }; - - Chance.prototype.exp = function (options) { - options = initOptions(options); - var exp = {}; - - exp.year = this.exp_year(); - - // If the year is this year, need to ensure month is greater than the - // current month or this expiration will not be valid - if (exp.year === (new Date().getFullYear()).toString()) { - exp.month = this.exp_month({future: true}); - } else { - exp.month = this.exp_month(); - } - - return options.raw ? exp : exp.month + '/' + exp.year; - }; - - Chance.prototype.exp_month = function (options) { - options = initOptions(options); - var month, month_int, - // Date object months are 0 indexed - curMonth = new Date().getMonth() + 1; - - if (options.future && (curMonth !== 12)) { - do { - month = this.month({raw: true}).numeric; - month_int = parseInt(month, 10); - } while (month_int <= curMonth); - } else { - month = this.month({raw: true}).numeric; - } - - return month; - }; - - Chance.prototype.exp_year = function () { - var curMonth = new Date().getMonth() + 1, - curYear = new Date().getFullYear(); - - return this.year({min: ((curMonth === 12) ? (curYear + 1) : curYear), max: (curYear + 10)}); - }; - - Chance.prototype.vat = function (options) { - options = initOptions(options, { country: 'it' }); - switch (options.country.toLowerCase()) { - case 'it': - return this.it_vat(); - } - }; - - /** - * Generate a string matching IBAN pattern (https://en.wikipedia.org/wiki/International_Bank_Account_Number). - * No country-specific formats support (yet) - */ - Chance.prototype.iban = function () { - var alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - var alphanum = alpha + '0123456789'; - var iban = - this.string({ length: 2, pool: alpha }) + - this.pad(this.integer({ min: 0, max: 99 }), 2) + - this.string({ length: 4, pool: alphanum }) + - this.pad(this.natural(), this.natural({ min: 6, max: 26 })); - return iban; - }; - - // -- End Finance - - // -- Regional - - Chance.prototype.it_vat = function () { - var it_vat = this.natural({min: 1, max: 1800000}); - - it_vat = this.pad(it_vat, 7) + this.pad(this.pick(this.provinces({ country: 'it' })).code, 3); - return it_vat + this.luhn_calculate(it_vat); - }; - - /* - * this generator is written following the official algorithm - * all data can be passed explicitely or randomized by calling chance.cf() without options - * the code does not check that the input data is valid (it goes beyond the scope of the generator) - * - * @param [Object] options = { first: first name, - * last: last name, - * gender: female|male, - birthday: JavaScript date object, - city: string(4), 1 letter + 3 numbers - } - * @return [string] codice fiscale - * - */ - Chance.prototype.cf = function (options) { - options = options || {}; - var gender = !!options.gender ? options.gender : this.gender(), - first = !!options.first ? options.first : this.first( { gender: gender, nationality: 'it'} ), - last = !!options.last ? options.last : this.last( { nationality: 'it'} ), - birthday = !!options.birthday ? options.birthday : this.birthday(), - city = !!options.city ? options.city : this.pickone(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'L', 'M', 'Z']) + this.pad(this.natural({max:999}), 3), - cf = [], - name_generator = function(name, isLast) { - var temp, - return_value = []; - - if (name.length < 3) { - return_value = name.split("").concat("XXX".split("")).splice(0,3); - } - else { - temp = name.toUpperCase().split('').map(function(c){ - return ("BCDFGHJKLMNPRSTVWZ".indexOf(c) !== -1) ? c : undefined; - }).join(''); - if (temp.length > 3) { - if (isLast) { - temp = temp.substr(0,3); - } else { - temp = temp[0] + temp.substr(2,2); - } - } - if (temp.length < 3) { - return_value = temp; - temp = name.toUpperCase().split('').map(function(c){ - return ("AEIOU".indexOf(c) !== -1) ? c : undefined; - }).join('').substr(0, 3 - return_value.length); - } - return_value = return_value + temp; - } - - return return_value; - }, - date_generator = function(birthday, gender, that) { - var lettermonths = ['A', 'B', 'C', 'D', 'E', 'H', 'L', 'M', 'P', 'R', 'S', 'T']; - - return birthday.getFullYear().toString().substr(2) + - lettermonths[birthday.getMonth()] + - that.pad(birthday.getDate() + ((gender.toLowerCase() === "female") ? 40 : 0), 2); - }, - checkdigit_generator = function(cf) { - var range1 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", - range2 = "ABCDEFGHIJABCDEFGHIJKLMNOPQRSTUVWXYZ", - evens = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", - odds = "BAKPLCQDREVOSFTGUHMINJWZYX", - digit = 0; - - - for(var i = 0; i < 15; i++) { - if (i % 2 !== 0) { - digit += evens.indexOf(range2[range1.indexOf(cf[i])]); - } - else { - digit += odds.indexOf(range2[range1.indexOf(cf[i])]); - } - } - return evens[digit % 26]; - }; - - cf = cf.concat(name_generator(last, true), name_generator(first), date_generator(birthday, gender, this), city.toUpperCase().split("")).join(""); - cf += checkdigit_generator(cf.toUpperCase(), this); - - return cf.toUpperCase(); - }; - - Chance.prototype.pl_pesel = function () { - var number = this.natural({min: 1, max: 9999999999}); - var arr = this.pad(number, 10).split(''); - for (var i = 0; i < arr.length; i++) { - arr[i] = parseInt(arr[i]); - } - - var controlNumber = (1 * arr[0] + 3 * arr[1] + 7 * arr[2] + 9 * arr[3] + 1 * arr[4] + 3 * arr[5] + 7 * arr[6] + 9 * arr[7] + 1 * arr[8] + 3 * arr[9]) % 10; - if(controlNumber !== 0) { - controlNumber = 10 - controlNumber; - } - - return arr.join('') + controlNumber; - }; - - Chance.prototype.pl_nip = function () { - var number = this.natural({min: 1, max: 999999999}); - var arr = this.pad(number, 9).split(''); - for (var i = 0; i < arr.length; i++) { - arr[i] = parseInt(arr[i]); - } - - var controlNumber = (6 * arr[0] + 5 * arr[1] + 7 * arr[2] + 2 * arr[3] + 3 * arr[4] + 4 * arr[5] + 5 * arr[6] + 6 * arr[7] + 7 * arr[8]) % 11; - if(controlNumber === 10) { - return this.pl_nip(); - } - - return arr.join('') + controlNumber; - }; - - Chance.prototype.pl_regon = function () { - var number = this.natural({min: 1, max: 99999999}); - var arr = this.pad(number, 8).split(''); - for (var i = 0; i < arr.length; i++) { - arr[i] = parseInt(arr[i]); - } - - var controlNumber = (8 * arr[0] + 9 * arr[1] + 2 * arr[2] + 3 * arr[3] + 4 * arr[4] + 5 * arr[5] + 6 * arr[6] + 7 * arr[7]) % 11; - if(controlNumber === 10) { - controlNumber = 0; - } - - return arr.join('') + controlNumber; - }; - - // -- End Regional - - // -- Music -- - - // Genre choices: - // Rock,Pop,Hip-Hop,Jazz,Classical,Electronic,Country,R&B,Reggae, - // Blues,Metal,Folk,Alternative,Punk,Disco,Funk,Techno, - // Indie,Gospel,Dance,Children's,World - - Chance.prototype.music_genre = function (genre = 'general') { - if (!(genre.toLowerCase() in data.music_genres)) { - throw new Error(`Unsupported genre: ${genre}`); - } - - const genres = data.music_genres[genre.toLowerCase()]; - const randomIndex = this.integer({ min: 0, max: genres.length - 1 }); - - return genres[randomIndex]; - }; - - Chance.prototype.note = function(options) { - // choices for 'notes' option: - // flatKey - chromatic scale with flat notes (default) - // sharpKey - chromatic scale with sharp notes - // flats - just flat notes - // sharps - just sharp notes - // naturals - just natural notes - // all - naturals, sharps and flats - options = initOptions(options, { notes : 'flatKey'}); - var scales = { - naturals: ['C', 'D', 'E', 'F', 'G', 'A', 'B'], - flats: ['D♭', 'E♭', 'G♭', 'A♭', 'B♭'], - sharps: ['C♯', 'D♯', 'F♯', 'G♯', 'A♯'] - }; - scales.all = scales.naturals.concat(scales.flats.concat(scales.sharps)) - scales.flatKey = scales.naturals.concat(scales.flats) - scales.sharpKey = scales.naturals.concat(scales.sharps) - return this.pickone(scales[options.notes]); - } - - Chance.prototype.midi_note = function(options) { - var min = 0; - var max = 127; - options = initOptions(options, { min : min, max : max }); - return this.integer({min: options.min, max: options.max}); - } - - Chance.prototype.chord_quality = function(options) { - options = initOptions(options, { jazz: true }); - var chord_qualities = ['maj', 'min', 'aug', 'dim']; - if (options.jazz){ - chord_qualities = [ - 'maj7', - 'min7', - '7', - 'sus', - 'dim', - 'ø' - ]; - } - return this.pickone(chord_qualities); - } - - Chance.prototype.chord = function (options) { - options = initOptions(options); - return this.note(options) + this.chord_quality(options); - } - - Chance.prototype.tempo = function (options) { - var min = 40; - var max = 320; - options = initOptions(options, {min: min, max: max}); - return this.integer({min: options.min, max: options.max}); - } - - // -- End Music - - // -- Miscellaneous -- - - // Coin - Flip, flip, flipadelphia - Chance.prototype.coin = function() { - return this.bool() ? "heads" : "tails"; - } - - // Dice - For all the board game geeks out there, myself included ;) - function diceFn (range) { - return function () { - return this.natural(range); - }; - } - Chance.prototype.d4 = diceFn({min: 1, max: 4}); - Chance.prototype.d6 = diceFn({min: 1, max: 6}); - Chance.prototype.d8 = diceFn({min: 1, max: 8}); - Chance.prototype.d10 = diceFn({min: 1, max: 10}); - Chance.prototype.d12 = diceFn({min: 1, max: 12}); - Chance.prototype.d20 = diceFn({min: 1, max: 20}); - Chance.prototype.d30 = diceFn({min: 1, max: 30}); - Chance.prototype.d100 = diceFn({min: 1, max: 100}); - - Chance.prototype.rpg = function (thrown, options) { - options = initOptions(options); - if (!thrown) { - throw new RangeError("Chance: A type of die roll must be included"); - } else { - var bits = thrown.toLowerCase().split("d"), - rolls = []; - - if (bits.length !== 2 || !parseInt(bits[0], 10) || !parseInt(bits[1], 10)) { - throw new Error("Chance: Invalid format provided. Please provide #d# where the first # is the number of dice to roll, the second # is the max of each die"); - } - for (var i = bits[0]; i > 0; i--) { - rolls[i - 1] = this.natural({min: 1, max: bits[1]}); - } - return (typeof options.sum !== 'undefined' && options.sum) ? rolls.reduce(function (p, c) { return p + c; }) : rolls; - } - }; - - // Guid - Chance.prototype.guid = function (options) { - options = initOptions(options, { version: 5 }); - - var guid_pool = "abcdef1234567890", - variant_pool = "ab89", - guid = this.string({ pool: guid_pool, length: 8 }) + '-' + - this.string({ pool: guid_pool, length: 4 }) + '-' + - // The Version - options.version + - this.string({ pool: guid_pool, length: 3 }) + '-' + - // The Variant - this.string({ pool: variant_pool, length: 1 }) + - this.string({ pool: guid_pool, length: 3 }) + '-' + - this.string({ pool: guid_pool, length: 12 }); - return guid; - }; - - // Hash - Chance.prototype.hash = function (options) { - options = initOptions(options, {length : 40, casing: 'lower'}); - var pool = options.casing === 'upper' ? HEX_POOL.toUpperCase() : HEX_POOL; - return this.string({pool: pool, length: options.length}); - }; - - Chance.prototype.luhn_check = function (num) { - var str = num.toString(); - var checkDigit = +str.substring(str.length - 1); - return checkDigit === this.luhn_calculate(+str.substring(0, str.length - 1)); - }; - - Chance.prototype.luhn_calculate = function (num) { - var digits = num.toString().split("").reverse(); - var sum = 0; - var digit; - - for (var i = 0, l = digits.length; l > i; ++i) { - digit = +digits[i]; - if (i % 2 === 0) { - digit *= 2; - if (digit > 9) { - digit -= 9; - } - } - sum += digit; - } - return (sum * 9) % 10; - }; - - // MD5 Hash - Chance.prototype.md5 = function(options) { - var opts = { str: '', key: null, raw: false }; - - if (!options) { - opts.str = this.string(); - options = {}; - } - else if (typeof options === 'string') { - opts.str = options; - options = {}; - } - else if (typeof options !== 'object') { - return null; - } - else if(options.constructor === 'Array') { - return null; - } - - opts = initOptions(options, opts); - - if(!opts.str){ - throw new Error('A parameter is required to return an md5 hash.'); - } - - return this.bimd5.md5(opts.str, opts.key, opts.raw); - }; - - /** - * #Description: - * ===================================================== - * Generate random file name with extension - * - * The argument provide extension type - * -> raster - * -> vector - * -> 3d - * -> document - * - * If nothing is provided the function return random file name with random - * extension type of any kind - * - * The user can validate the file name length range - * If nothing provided the generated file name is random - * - * #Extension Pool : - * * Currently the supported extensions are - * -> some of the most popular raster image extensions - * -> some of the most popular vector image extensions - * -> some of the most popular 3d image extensions - * -> some of the most popular document extensions - * - * #Examples : - * ===================================================== - * - * Return random file name with random extension. The file extension - * is provided by a predefined collection of extensions. More about the extension - * pool can be found in #Extension Pool section - * - * chance.file() - * => dsfsdhjf.xml - * - * In order to generate a file name with specific length, specify the - * length property and integer value. The extension is going to be random - * - * chance.file({length : 10}) - * => asrtineqos.pdf - * - * In order to generate file with extension from some of the predefined groups - * of the extension pool just specify the extension pool category in fileType property - * - * chance.file({fileType : 'raster'}) - * => dshgssds.psd - * - * You can provide specific extension for your files - * chance.file({extension : 'html'}) - * => djfsd.html - * - * Or you could pass custom collection of extensions by array or by object - * chance.file({extensions : [...]}) - * => dhgsdsd.psd - * - * chance.file({extensions : { key : [...], key : [...]}}) - * => djsfksdjsd.xml - * - * @param [collection] options - * @return [string] - * - */ - Chance.prototype.file = function(options) { - - var fileOptions = options || {}; - var poolCollectionKey = "fileExtension"; - var typeRange = Object.keys(this.get("fileExtension"));//['raster', 'vector', '3d', 'document']; - var fileName; - var fileExtension; - - // Generate random file name - fileName = this.word({length : fileOptions.length}); - - // Generate file by specific extension provided by the user - if(fileOptions.extension) { - - fileExtension = fileOptions.extension; - return (fileName + '.' + fileExtension); - } - - // Generate file by specific extension collection - if(fileOptions.extensions) { - - if(Array.isArray(fileOptions.extensions)) { - - fileExtension = this.pickone(fileOptions.extensions); - return (fileName + '.' + fileExtension); - } - else if(fileOptions.extensions.constructor === Object) { - - var extensionObjectCollection = fileOptions.extensions; - var keys = Object.keys(extensionObjectCollection); - - fileExtension = this.pickone(extensionObjectCollection[this.pickone(keys)]); - return (fileName + '.' + fileExtension); - } - - throw new Error("Chance: Extensions must be an Array or Object"); - } - - // Generate file extension based on specific file type - if(fileOptions.fileType) { - - var fileType = fileOptions.fileType; - if(typeRange.indexOf(fileType) !== -1) { - - fileExtension = this.pickone(this.get(poolCollectionKey)[fileType]); - return (fileName + '.' + fileExtension); - } - - throw new RangeError("Chance: Expect file type value to be 'raster', 'vector', '3d' or 'document'"); - } - - // Generate random file name if no extension options are passed - fileExtension = this.pickone(this.get(poolCollectionKey)[this.pickone(typeRange)]); - return (fileName + '.' + fileExtension); - }; - - /** - * Generates file data of random bytes using the chance.file method for the file name - * - * @param {object} - * fileName: String - * fileExtention: String - * fileSize: Number <- in bytes - * @returns {object} fileName: String, fileData: Buffer - */ - Chance.prototype.fileWithContent = function (options){ - var fileOptions = options || {}; - var fileName = 'fileName' in fileOptions ? fileOptions.fileName : this.file().split(".")[0]; - fileName += "." + ('fileExtension' in fileOptions ? fileOptions.fileExtension : this.file().split(".")[1]); - - - if (typeof fileOptions.fileSize !== "number") { - throw new Error('File size must be an integer') - } - var file = { - fileData: this.buffer({length: fileOptions.fileSize}), - fileName: fileName, - }; - return file; - } - - var data = { - - firstNames: { - "male": { - "en": ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Charles", "Thomas", "Christopher", "Daniel", "Matthew", "George", "Donald", "Anthony", "Paul", "Mark", "Edward", "Steven", "Kenneth", "Andrew", "Brian", "Joshua", "Kevin", "Ronald", "Timothy", "Jason", "Jeffrey", "Frank", "Gary", "Ryan", "Nicholas", "Eric", "Stephen", "Jacob", "Larry", "Jonathan", "Scott", "Raymond", "Justin", "Brandon", "Gregory", "Samuel", "Benjamin", "Patrick", "Jack", "Henry", "Walter", "Dennis", "Jerry", "Alexander", "Peter", "Tyler", "Douglas", "Harold", "Aaron", "Jose", "Adam", "Arthur", "Zachary", "Carl", "Nathan", "Albert", "Kyle", "Lawrence", "Joe", "Willie", "Gerald", "Roger", "Keith", "Jeremy", "Terry", "Harry", "Ralph", "Sean", "Jesse", "Roy", "Louis", "Billy", "Austin", "Bruce", "Eugene", "Christian", "Bryan", "Wayne", "Russell", "Howard", "Fred", "Ethan", "Jordan", "Philip", "Alan", "Juan", "Randy", "Vincent", "Bobby", "Dylan", "Johnny", "Phillip", "Victor", "Clarence", "Ernest", "Martin", "Craig", "Stanley", "Shawn", "Travis", "Bradley", "Leonard", "Earl", "Gabriel", "Jimmy", "Francis", "Todd", "Noah", "Danny", "Dale", "Cody", "Carlos", "Allen", "Frederick", "Logan", "Curtis", "Alex", "Joel", "Luis", "Norman", "Marvin", "Glenn", "Tony", "Nathaniel", "Rodney", "Melvin", "Alfred", "Steve", "Cameron", "Chad", "Edwin", "Caleb", "Evan", "Antonio", "Lee", "Herbert", "Jeffery", "Isaac", "Derek", "Ricky", "Marcus", "Theodore", "Elijah", "Luke", "Jesus", "Eddie", "Troy", "Mike", "Dustin", "Ray", "Adrian", "Bernard", "Leroy", "Angel", "Randall", "Wesley", "Ian", "Jared", "Mason", "Hunter", "Calvin", "Oscar", "Clifford", "Jay", "Shane", "Ronnie", "Barry", "Lucas", "Corey", "Manuel", "Leo", "Tommy", "Warren", "Jackson", "Isaiah", "Connor", "Don", "Dean", "Jon", "Julian", "Miguel", "Bill", "Lloyd", "Charlie", "Mitchell", "Leon", "Jerome", "Darrell", "Jeremiah", "Alvin", "Brett", "Seth", "Floyd", "Jim", "Blake", "Micheal", "Gordon", "Trevor", "Lewis", "Erik", "Edgar", "Vernon", "Devin", "Gavin", "Jayden", "Chris", "Clyde", "Tom", "Derrick", "Mario", "Brent", "Marc", "Herman", "Chase", "Dominic", "Ricardo", "Franklin", "Maurice", "Max", "Aiden", "Owen", "Lester", "Gilbert", "Elmer", "Gene", "Francisco", "Glen", "Cory", "Garrett", "Clayton", "Sam", "Jorge", "Chester", "Alejandro", "Jeff", "Harvey", "Milton", "Cole", "Ivan", "Andre", "Duane", "Landon"], - // Data taken from http://www.dati.gov.it/dataset/comune-di-firenze_0163 - "it": ["Adolfo", "Alberto", "Aldo", "Alessandro", "Alessio", "Alfredo", "Alvaro", "Andrea", "Angelo", "Angiolo", "Antonino", "Antonio", "Attilio", "Benito", "Bernardo", "Bruno", "Carlo", "Cesare", "Christian", "Claudio", "Corrado", "Cosimo", "Cristian", "Cristiano", "Daniele", "Dario", "David", "Davide", "Diego", "Dino", "Domenico", "Duccio", "Edoardo", "Elia", "Elio", "Emanuele", "Emiliano", "Emilio", "Enrico", "Enzo", "Ettore", "Fabio", "Fabrizio", "Federico", "Ferdinando", "Fernando", "Filippo", "Francesco", "Franco", "Gabriele", "Giacomo", "Giampaolo", "Giampiero", "Giancarlo", "Gianfranco", "Gianluca", "Gianmarco", "Gianni", "Gino", "Giorgio", "Giovanni", "Giuliano", "Giulio", "Giuseppe", "Graziano", "Gregorio", "Guido", "Iacopo", "Jacopo", "Lapo", "Leonardo", "Lorenzo", "Luca", "Luciano", "Luigi", "Manuel", "Marcello", "Marco", "Marino", "Mario", "Massimiliano", "Massimo", "Matteo", "Mattia", "Maurizio", "Mauro", "Michele", "Mirko", "Mohamed", "Nello", "Neri", "Niccolò", "Nicola", "Osvaldo", "Otello", "Paolo", "Pier Luigi", "Piero", "Pietro", "Raffaele", "Remo", "Renato", "Renzo", "Riccardo", "Roberto", "Rolando", "Romano", "Salvatore", "Samuele", "Sandro", "Sergio", "Silvano", "Simone", "Stefano", "Thomas", "Tommaso", "Ubaldo", "Ugo", "Umberto", "Valerio", "Valter", "Vasco", "Vincenzo", "Vittorio"], - // Data taken from http://www.svbkindernamen.nl/int/nl/kindernamen/index.html - "nl": ["Aaron","Abel","Adam","Adriaan","Albert","Alexander","Ali","Arjen","Arno","Bart","Bas","Bastiaan","Benjamin","Bob", "Boris","Bram","Brent","Cas","Casper","Chris","Christiaan","Cornelis","Daan","Daley","Damian","Dani","Daniel","Daniël","David","Dean","Dirk","Dylan","Egbert","Elijah","Erik","Erwin","Evert","Ezra","Fabian","Fedde","Finn","Florian","Floris","Frank","Frans","Frederik","Freek","Geert","Gerard","Gerben","Gerrit","Gijs","Guus","Hans","Hendrik","Henk","Herman","Hidde","Hugo","Jaap","Jan Jaap","Jan-Willem","Jack","Jacob","Jan","Jason","Jasper","Jayden","Jelle","Jelte","Jens","Jeroen","Jesse","Jim","Job","Joep","Johannes","John","Jonathan","Joris","Joshua","Joël","Julian","Kees","Kevin","Koen","Lars","Laurens","Leendert","Lennard","Lodewijk","Luc","Luca","Lucas","Lukas","Luuk","Maarten","Marcus","Martijn","Martin","Matthijs","Maurits","Max","Mees","Melle","Mick","Mika","Milan","Mohamed","Mohammed","Morris","Muhammed","Nathan","Nick","Nico","Niek","Niels","Noah","Noud","Olivier","Oscar","Owen","Paul","Pepijn","Peter","Pieter","Pim","Quinten","Reinier","Rens","Robin","Ruben","Sam","Samuel","Sander","Sebastiaan","Sem","Sep","Sepp","Siem","Simon","Stan","Stef","Steven","Stijn","Sven","Teun","Thijmen","Thijs","Thomas","Tijn","Tim","Timo","Tobias","Tom","Victor","Vince","Willem","Wim","Wouter","Yusuf"], - // Data taken from https://fr.wikipedia.org/wiki/Liste_de_pr%C3%A9noms_fran%C3%A7ais_et_de_la_francophonie - "fr": ["Aaron","Abdon","Abel","Abélard","Abelin","Abondance","Abraham","Absalon","Acace","Achaire","Achille","Adalard","Adalbald","Adalbéron","Adalbert","Adalric","Adam","Adegrin","Adel","Adelin","Andelin","Adelphe","Adam","Adéodat","Adhémar","Adjutor","Adolphe","Adonis","Adon","Adrien","Agapet","Agathange","Agathon","Agilbert","Agénor","Agnan","Aignan","Agrippin","Aimable","Aimé","Alain","Alban","Albin","Aubin","Albéric","Albert","Albertet","Alcibiade","Alcide","Alcée","Alcime","Aldonce","Aldric","Aldéric","Aleaume","Alexandre","Alexis","Alix","Alliaume","Aleaume","Almine","Almire","Aloïs","Alphée","Alphonse","Alpinien","Alverède","Amalric","Amaury","Amandin","Amant","Ambroise","Amédée","Amélien","Amiel","Amour","Anaël","Anastase","Anatole","Ancelin","Andéol","Andoche","André","Andoche","Ange","Angelin","Angilbe","Anglebert","Angoustan","Anicet","Anne","Annibal","Ansbert","Anselme","Anthelme","Antheaume","Anthime","Antide","Antoine","Antonius","Antonin","Apollinaire","Apollon","Aquilin","Arcade","Archambaud","Archambeau","Archange","Archibald","Arian","Ariel","Ariste","Aristide","Armand","Armel","Armin","Arnould","Arnaud","Arolde","Arsène","Arsinoé","Arthaud","Arthème","Arthur","Ascelin","Athanase","Aubry","Audebert","Audouin","Audran","Audric","Auguste","Augustin","Aurèle","Aurélien","Aurian","Auxence","Axel","Aymard","Aymeric","Aymon","Aymond","Balthazar","Baptiste","Barnabé","Barthélemy","Bartimée","Basile","Bastien","Baudouin","Bénigne","Benjamin","Benoît","Bérenger","Bérard","Bernard","Bertrand","Blaise","Bon","Boniface","Bouchard","Brice","Brieuc","Bruno","Brunon","Calixte","Calliste","Camélien","Camille","Camillien","Candide","Caribert","Carloman","Cassandre","Cassien","Cédric","Céleste","Célestin","Célien","Césaire","César","Charles","Charlemagne","Childebert","Chilpéric","Chrétien","Christian","Christodule","Christophe","Chrysostome","Clarence","Claude","Claudien","Cléandre","Clément","Clotaire","Côme","Constance","Constant","Constantin","Corentin","Cyprien","Cyriaque","Cyrille","Cyril","Damien","Daniel","David","Delphin","Denis","Désiré","Didier","Dieudonné","Dimitri","Dominique","Dorian","Dorothée","Edgard","Edmond","Édouard","Éleuthère","Élie","Élisée","Émeric","Émile","Émilien","Emmanuel","Enguerrand","Épiphane","Éric","Esprit","Ernest","Étienne","Eubert","Eudes","Eudoxe","Eugène","Eusèbe","Eustache","Évariste","Évrard","Fabien","Fabrice","Falba","Félicité","Félix","Ferdinand","Fiacre","Fidèle","Firmin","Flavien","Flodoard","Florent","Florentin","Florestan","Florian","Fortuné","Foulques","Francisque","François","Français","Franciscus","Francs","Frédéric","Fulbert","Fulcran","Fulgence","Gabin","Gabriel","Gaël","Garnier","Gaston","Gaspard","Gatien","Gaud","Gautier","Gédéon","Geoffroy","Georges","Géraud","Gérard","Gerbert","Germain","Gervais","Ghislain","Gilbert","Gilles","Girart","Gislebert","Gondebaud","Gonthier","Gontran","Gonzague","Grégoire","Guérin","Gui","Guillaume","Gustave","Guy","Guyot","Hardouin","Hector","Hédelin","Hélier","Henri","Herbert","Herluin","Hervé","Hilaire","Hildebert","Hincmar","Hippolyte","Honoré","Hubert","Hugues","Innocent","Isabeau","Isidore","Jacques","Japhet","Jason","Jean","Jeannel","Jeannot","Jérémie","Jérôme","Joachim","Joanny","Job","Jocelyn","Joël","Johan","Jonas","Jonathan","Joseph","Josse","Josselin","Jourdain","Jude","Judicaël","Jules","Julien","Juste","Justin","Lambert","Landry","Laurent","Lazare","Léandre","Léon","Léonard","Léopold","Leu","Loup","Leufroy","Libère","Liétald","Lionel","Loïc","Longin","Lorrain","Lorraine","Lothaire","Louis","Loup","Luc","Lucas","Lucien","Ludolphe","Ludovic","Macaire","Malo","Mamert","Manassé","Marc","Marceau","Marcel","Marcelin","Marius","Marseille","Martial","Martin","Mathurin","Matthias","Mathias","Matthieu","Maugis","Maurice","Mauricet","Maxence","Maxime","Maximilien","Mayeul","Médéric","Melchior","Mence","Merlin","Mérovée","Michaël","Michel","Moïse","Morgan","Nathan","Nathanaël","Narcisse","Néhémie","Nestor","Nestor","Nicéphore","Nicolas","Noé","Noël","Norbert","Normand","Normands","Octave","Odilon","Odon","Oger","Olivier","Oury","Pacôme","Palémon","Parfait","Pascal","Paterne","Patrice","Paul","Pépin","Perceval","Philémon","Philibert","Philippe","Philothée","Pie","Pierre","Pierrick","Prosper","Quentin","Raoul","Raphaël","Raymond","Régis","Réjean","Rémi","Renaud","René","Reybaud","Richard","Robert","Roch","Rodolphe","Rodrigue","Roger","Roland","Romain","Romuald","Roméo","Rome","Ronan","Roselin","Salomon","Samuel","Savin","Savinien","Scholastique","Sébastien","Séraphin","Serge","Séverin","Sidoine","Sigebert","Sigismond","Silvère","Simon","Siméon","Sixte","Stanislas","Stéphane","Stephan","Sylvain","Sylvestre","Tancrède","Tanguy","Taurin","Théodore","Théodose","Théophile","Théophraste","Thibault","Thibert","Thierry","Thomas","Timoléon","Timothée","Titien","Tonnin","Toussaint","Trajan","Tristan","Turold","Tim","Ulysse","Urbain","Valentin","Valère","Valéry","Venance","Venant","Venceslas","Vianney","Victor","Victorien","Victorin","Vigile","Vincent","Vital","Vitalien","Vivien","Waleran","Wandrille","Xavier","Xénophon","Yves","Zacharie","Zaché","Zéphirin"] - }, - - "female": { - "en": ["Mary", "Emma", "Elizabeth", "Minnie", "Margaret", "Ida", "Alice", "Bertha", "Sarah", "Annie", "Clara", "Ella", "Florence", "Cora", "Martha", "Laura", "Nellie", "Grace", "Carrie", "Maude", "Mabel", "Bessie", "Jennie", "Gertrude", "Julia", "Hattie", "Edith", "Mattie", "Rose", "Catherine", "Lillian", "Ada", "Lillie", "Helen", "Jessie", "Louise", "Ethel", "Lula", "Myrtle", "Eva", "Frances", "Lena", "Lucy", "Edna", "Maggie", "Pearl", "Daisy", "Fannie", "Josephine", "Dora", "Rosa", "Katherine", "Agnes", "Marie", "Nora", "May", "Mamie", "Blanche", "Stella", "Ellen", "Nancy", "Effie", "Sallie", "Nettie", "Della", "Lizzie", "Flora", "Susie", "Maud", "Mae", "Etta", "Harriet", "Sadie", "Caroline", "Katie", "Lydia", "Elsie", "Kate", "Susan", "Mollie", "Alma", "Addie", "Georgia", "Eliza", "Lulu", "Nannie", "Lottie", "Amanda", "Belle", "Charlotte", "Rebecca", "Ruth", "Viola", "Olive", "Amelia", "Hannah", "Jane", "Virginia", "Emily", "Matilda", "Irene", "Kathryn", "Esther", "Willie", "Henrietta", "Ollie", "Amy", "Rachel", "Sara", "Estella", "Theresa", "Augusta", "Ora", "Pauline", "Josie", "Lola", "Sophia", "Leona", "Anne", "Mildred", "Ann", "Beulah", "Callie", "Lou", "Delia", "Eleanor", "Barbara", "Iva", "Louisa", "Maria", "Mayme", "Evelyn", "Estelle", "Nina", "Betty", "Marion", "Bettie", "Dorothy", "Luella", "Inez", "Lela", "Rosie", "Allie", "Millie", "Janie", "Cornelia", "Victoria", "Ruby", "Winifred", "Alta", "Celia", "Christine", "Beatrice", "Birdie", "Harriett", "Mable", "Myra", "Sophie", "Tillie", "Isabel", "Sylvia", "Carolyn", "Isabelle", "Leila", "Sally", "Ina", "Essie", "Bertie", "Nell", "Alberta", "Katharine", "Lora", "Rena", "Mina", "Rhoda", "Mathilda", "Abbie", "Eula", "Dollie", "Hettie", "Eunice", "Fanny", "Ola", "Lenora", "Adelaide", "Christina", "Lelia", "Nelle", "Sue", "Johanna", "Lilly", "Lucinda", "Minerva", "Lettie", "Roxie", "Cynthia", "Helena", "Hilda", "Hulda", "Bernice", "Genevieve", "Jean", "Cordelia", "Marian", "Francis", "Jeanette", "Adeline", "Gussie", "Leah", "Lois", "Lura", "Mittie", "Hallie", "Isabella", "Olga", "Phoebe", "Teresa", "Hester", "Lida", "Lina", "Winnie", "Claudia", "Marguerite", "Vera", "Cecelia", "Bess", "Emilie", "Rosetta", "Verna", "Myrtie", "Cecilia", "Elva", "Olivia", "Ophelia", "Georgie", "Elnora", "Violet", "Adele", "Lily", "Linnie", "Loretta", "Madge", "Polly", "Virgie", "Eugenia", "Lucile", "Lucille", "Mabelle", "Rosalie"], - // Data taken from http://www.dati.gov.it/dataset/comune-di-firenze_0162 - "it": ["Ada", "Adriana", "Alessandra", "Alessia", "Alice", "Angela", "Anna", "Anna Maria", "Annalisa", "Annita", "Annunziata", "Antonella", "Arianna", "Asia", "Assunta", "Aurora", "Barbara", "Beatrice", "Benedetta", "Bianca", "Bruna", "Camilla", "Carla", "Carlotta", "Carmela", "Carolina", "Caterina", "Catia", "Cecilia", "Chiara", "Cinzia", "Clara", "Claudia", "Costanza", "Cristina", "Daniela", "Debora", "Diletta", "Dina", "Donatella", "Elena", "Eleonora", "Elisa", "Elisabetta", "Emanuela", "Emma", "Eva", "Federica", "Fernanda", "Fiorella", "Fiorenza", "Flora", "Franca", "Francesca", "Gabriella", "Gaia", "Gemma", "Giada", "Gianna", "Gina", "Ginevra", "Giorgia", "Giovanna", "Giulia", "Giuliana", "Giuseppa", "Giuseppina", "Grazia", "Graziella", "Greta", "Ida", "Ilaria", "Ines", "Iolanda", "Irene", "Irma", "Isabella", "Jessica", "Laura", "Lea", "Letizia", "Licia", "Lidia", "Liliana", "Lina", "Linda", "Lisa", "Livia", "Loretta", "Luana", "Lucia", "Luciana", "Lucrezia", "Luisa", "Manuela", "Mara", "Marcella", "Margherita", "Maria", "Maria Cristina", "Maria Grazia", "Maria Luisa", "Maria Pia", "Maria Teresa", "Marina", "Marisa", "Marta", "Martina", "Marzia", "Matilde", "Melissa", "Michela", "Milena", "Mirella", "Monica", "Natalina", "Nella", "Nicoletta", "Noemi", "Olga", "Paola", "Patrizia", "Piera", "Pierina", "Raffaella", "Rebecca", "Renata", "Rina", "Rita", "Roberta", "Rosa", "Rosanna", "Rossana", "Rossella", "Sabrina", "Sandra", "Sara", "Serena", "Silvana", "Silvia", "Simona", "Simonetta", "Sofia", "Sonia", "Stefania", "Susanna", "Teresa", "Tina", "Tiziana", "Tosca", "Valentina", "Valeria", "Vanda", "Vanessa", "Vanna", "Vera", "Veronica", "Vilma", "Viola", "Virginia", "Vittoria"], - // Data taken from http://www.svbkindernamen.nl/int/nl/kindernamen/index.html - "nl": ["Ada", "Arianne", "Afke", "Amanda", "Amber", "Amy", "Aniek", "Anita", "Anja", "Anna", "Anne", "Annelies", "Annemarie", "Annette", "Anouk", "Astrid", "Aukje", "Barbara", "Bianca", "Carla", "Carlijn", "Carolien", "Chantal", "Charlotte", "Claudia", "Daniëlle", "Debora", "Diane", "Dora", "Eline", "Elise", "Ella", "Ellen", "Emma", "Esmee", "Evelien", "Esther", "Erica", "Eva", "Femke", "Fleur", "Floor", "Froukje", "Gea", "Gerda", "Hanna", "Hanneke", "Heleen", "Hilde", "Ilona", "Ina", "Inge", "Ingrid", "Iris", "Isabel", "Isabelle", "Janneke", "Jasmijn", "Jeanine", "Jennifer", "Jessica", "Johanna", "Joke", "Julia", "Julie", "Karen", "Karin", "Katja", "Kim", "Lara", "Laura", "Lena", "Lianne", "Lieke", "Lilian", "Linda", "Lisa", "Lisanne", "Lotte", "Louise", "Maaike", "Manon", "Marga", "Maria", "Marissa", "Marit", "Marjolein", "Martine", "Marleen", "Melissa", "Merel", "Miranda", "Michelle", "Mirjam", "Mirthe", "Naomi", "Natalie", 'Nienke', "Nina", "Noortje", "Olivia", "Patricia", "Paula", "Paulien", "Ramona", "Ria", "Rianne", "Roos", "Rosanne", "Ruth", "Sabrina", "Sandra", "Sanne", "Sara", "Saskia", "Silvia", "Sofia", "Sophie", "Sonja", "Suzanne", "Tamara", "Tess", "Tessa", "Tineke", "Valerie", "Vanessa", "Veerle", "Vera", "Victoria", "Wendy", "Willeke", "Yvonne", "Zoë"], - // Data taken from https://fr.wikipedia.org/wiki/Liste_de_pr%C3%A9noms_fran%C3%A7ais_et_de_la_francophonie - "fr": ["Abdon","Abel","Abigaëlle","Abigaïl","Acacius","Acanthe","Adalbert","Adalsinde","Adegrine","Adélaïde","Adèle","Adélie","Adeline","Adeltrude","Adolphe","Adonis","Adrastée","Adrehilde","Adrienne","Agathe","Agilbert","Aglaé","Aignan","Agneflète","Agnès","Agrippine","Aimé","Alaine","Alaïs","Albane","Albérade","Alberte","Alcide","Alcine","Alcyone","Aldegonde","Aleth","Alexandrine","Alexine","Alice","Aliénor","Aliette","Aline","Alix","Alizé","Aloïse","Aloyse","Alphonsine","Althée","Amaliane","Amalthée","Amande","Amandine","Amant","Amarande","Amaranthe","Amaryllis","Ambre","Ambroisie","Amélie","Améthyste","Aminte","Anaël","Anaïs","Anastasie","Anatole","Ancelin","Andrée","Anémone","Angadrême","Angèle","Angeline","Angélique","Angilbert","Anicet","Annabelle","Anne","Annette","Annick","Annie","Annonciade","Ansbert","Anstrudie","Anthelme","Antigone","Antoinette","Antonine","Aphélie","Apolline","Apollonie","Aquiline","Arabelle","Arcadie","Archange","Argine","Ariane","Aricie","Ariel","Arielle","Arlette","Armance","Armande","Armandine","Armelle","Armide","Armelle","Armin","Arnaud","Arsène","Arsinoé","Artémis","Arthur","Ascelin","Ascension","Assomption","Astarté","Astérie","Astrée","Astrid","Athalie","Athanasie","Athina","Aube","Albert","Aude","Audrey","Augustine","Aure","Aurélie","Aurélien","Aurèle","Aurore","Auxence","Aveline","Abigaëlle","Avoye","Axelle","Aymard","Azalée","Adèle","Adeline","Barbe","Basilisse","Bathilde","Béatrice","Béatrix","Bénédicte","Bérengère","Bernadette","Berthe","Bertille","Beuve","Blanche","Blanc","Blandine","Brigitte","Brune","Brunehilde","Callista","Camille","Capucine","Carine","Caroline","Cassandre","Catherine","Cécile","Céleste","Célestine","Céline","Chantal","Charlène","Charline","Charlotte","Chloé","Christelle","Christiane","Christine","Claire","Clara","Claude","Claudine","Clarisse","Clémence","Clémentine","Cléo","Clio","Clotilde","Coline","Conception","Constance","Coralie","Coraline","Corentine","Corinne","Cyrielle","Daniel","Daniel","Daphné","Débora","Delphine","Denise","Diane","Dieudonné","Dominique","Doriane","Dorothée","Douce","Édith","Edmée","Éléonore","Éliane","Élia","Éliette","Élisabeth","Élise","Ella","Élodie","Éloïse","Elsa","Émeline","Émérance","Émérentienne","Émérencie","Émilie","Emma","Emmanuelle","Emmelie","Ernestine","Esther","Estelle","Eudoxie","Eugénie","Eulalie","Euphrasie","Eusébie","Évangéline","Eva","Ève","Évelyne","Fanny","Fantine","Faustine","Félicie","Fernande","Flavie","Fleur","Flore","Florence","Florie","Fortuné","France","Francia","Françoise","Francine","Gabrielle","Gaëlle","Garance","Geneviève","Georgette","Gerberge","Germaine","Gertrude","Gisèle","Guenièvre","Guilhemine","Guillemette","Gustave","Gwenael","Hélène","Héloïse","Henriette","Hermine","Hermione","Hippolyte","Honorine","Hortense","Huguette","Ines","Irène","Irina","Iris","Isabeau","Isabelle","Iseult","Isolde","Ismérie","Jacinthe","Jacqueline","Jade","Janine","Jeanne","Jocelyne","Joëlle","Joséphine","Judith","Julia","Julie","Jules","Juliette","Justine","Katy","Kathy","Katie","Laura","Laure","Laureline","Laurence","Laurene","Lauriane","Laurianne","Laurine","Léa","Léna","Léonie","Léon","Léontine","Lorraine","Lucie","Lucienne","Lucille","Ludivine","Lydie","Lydie","Megane","Madeleine","Magali","Maguelone","Mallaury","Manon","Marceline","Margot","Marguerite","Marianne","Marie","Myriam","Marie","Marine","Marion","Marlène","Marthe","Martine","Mathilde","Maud","Maureen","Mauricette","Maxime","Mélanie","Melissa","Mélissandre","Mélisande","Mélodie","Michel","Micheline","Mireille","Miriam","Moïse","Monique","Morgane","Muriel","Mylène","Nadège","Nadine","Nathalie","Nicole","Nicolette","Nine","Noël","Noémie","Océane","Odette","Odile","Olive","Olivia","Olympe","Ombline","Ombeline","Ophélie","Oriande","Oriane","Ozanne","Pascale","Pascaline","Paule","Paulette","Pauline","Priscille","Prisca","Prisque","Pécine","Pélagie","Pénélope","Perrine","Pétronille","Philippine","Philomène","Philothée","Primerose","Prudence","Pulchérie","Quentine","Quiéta","Quintia","Quintilla","Rachel","Raphaëlle","Raymonde","Rebecca","Régine","Réjeanne","René","Rita","Rita","Rolande","Romane","Rosalie","Rose","Roseline","Sabine","Salomé","Sandra","Sandrine","Sarah","Ségolène","Séverine","Sibylle","Simone","Sixt","Solange","Soline","Solène","Sophie","Stéphanie","Suzanne","Sylvain","Sylvie","Tatiana","Thaïs","Théodora","Thérèse","Tiphaine","Ursule","Valentine","Valérie","Véronique","Victoire","Victorine","Vinciane","Violette","Virginie","Viviane","Xavière","Yolande","Ysaline","Yvette","Yvonne","Zélie","Zita","Zoé"] - } - }, - - lastNames: { - "en": ['Smith', 'Johnson', 'Williams', 'Jones', 'Brown', 'Davis', 'Miller', 'Wilson', 'Moore', 'Taylor', 'Anderson', 'Thomas', 'Jackson', 'White', 'Harris', 'Martin', 'Thompson', 'Garcia', 'Martinez', 'Robinson', 'Clark', 'Rodriguez', 'Lewis', 'Lee', 'Walker', 'Hall', 'Allen', 'Young', 'Hernandez', 'King', 'Wright', 'Lopez', 'Hill', 'Scott', 'Green', 'Adams', 'Baker', 'Gonzalez', 'Nelson', 'Carter', 'Mitchell', 'Perez', 'Roberts', 'Turner', 'Phillips', 'Campbell', 'Parker', 'Evans', 'Edwards', 'Collins', 'Stewart', 'Sanchez', 'Morris', 'Rogers', 'Reed', 'Cook', 'Morgan', 'Bell', 'Murphy', 'Bailey', 'Rivera', 'Cooper', 'Richardson', 'Cox', 'Howard', 'Ward', 'Torres', 'Peterson', 'Gray', 'Ramirez', 'James', 'Watson', 'Brooks', 'Kelly', 'Sanders', 'Price', 'Bennett', 'Wood', 'Barnes', 'Ross', 'Henderson', 'Coleman', 'Jenkins', 'Perry', 'Powell', 'Long', 'Patterson', 'Hughes', 'Flores', 'Washington', 'Butler', 'Simmons', 'Foster', 'Gonzales', 'Bryant', 'Alexander', 'Russell', 'Griffin', 'Diaz', 'Hayes', 'Myers', 'Ford', 'Hamilton', 'Graham', 'Sullivan', 'Wallace', 'Woods', 'Cole', 'West', 'Jordan', 'Owens', 'Reynolds', 'Fisher', 'Ellis', 'Harrison', 'Gibson', 'McDonald', 'Cruz', 'Marshall', 'Ortiz', 'Gomez', 'Murray', 'Freeman', 'Wells', 'Webb', 'Simpson', 'Stevens', 'Tucker', 'Porter', 'Hunter', 'Hicks', 'Crawford', 'Henry', 'Boyd', 'Mason', 'Morales', 'Kennedy', 'Warren', 'Dixon', 'Ramos', 'Reyes', 'Burns', 'Gordon', 'Shaw', 'Holmes', 'Rice', 'Robertson', 'Hunt', 'Black', 'Daniels', 'Palmer', 'Mills', 'Nichols', 'Grant', 'Knight', 'Ferguson', 'Rose', 'Stone', 'Hawkins', 'Dunn', 'Perkins', 'Hudson', 'Spencer', 'Gardner', 'Stephens', 'Payne', 'Pierce', 'Berry', 'Matthews', 'Arnold', 'Wagner', 'Willis', 'Ray', 'Watkins', 'Olson', 'Carroll', 'Duncan', 'Snyder', 'Hart', 'Cunningham', 'Bradley', 'Lane', 'Andrews', 'Ruiz', 'Harper', 'Fox', 'Riley', 'Armstrong', 'Carpenter', 'Weaver', 'Greene', 'Lawrence', 'Elliott', 'Chavez', 'Sims', 'Austin', 'Peters', 'Kelley', 'Franklin', 'Lawson', 'Fields', 'Gutierrez', 'Ryan', 'Schmidt', 'Carr', 'Vasquez', 'Castillo', 'Wheeler', 'Chapman', 'Oliver', 'Montgomery', 'Richards', 'Williamson', 'Johnston', 'Banks', 'Meyer', 'Bishop', 'McCoy', 'Howell', 'Alvarez', 'Morrison', 'Hansen', 'Fernandez', 'Garza', 'Harvey', 'Little', 'Burton', 'Stanley', 'Nguyen', 'George', 'Jacobs', 'Reid', 'Kim', 'Fuller', 'Lynch', 'Dean', 'Gilbert', 'Garrett', 'Romero', 'Welch', 'Larson', 'Frazier', 'Burke', 'Hanson', 'Day', 'Mendoza', 'Moreno', 'Bowman', 'Medina', 'Fowler', 'Brewer', 'Hoffman', 'Carlson', 'Silva', 'Pearson', 'Holland', 'Douglas', 'Fleming', 'Jensen', 'Vargas', 'Byrd', 'Davidson', 'Hopkins', 'May', 'Terry', 'Herrera', 'Wade', 'Soto', 'Walters', 'Curtis', 'Neal', 'Caldwell', 'Lowe', 'Jennings', 'Barnett', 'Graves', 'Jimenez', 'Horton', 'Shelton', 'Barrett', 'Obrien', 'Castro', 'Sutton', 'Gregory', 'McKinney', 'Lucas', 'Miles', 'Craig', 'Rodriquez', 'Chambers', 'Holt', 'Lambert', 'Fletcher', 'Watts', 'Bates', 'Hale', 'Rhodes', 'Pena', 'Beck', 'Newman', 'Haynes', 'McDaniel', 'Mendez', 'Bush', 'Vaughn', 'Parks', 'Dawson', 'Santiago', 'Norris', 'Hardy', 'Love', 'Steele', 'Curry', 'Powers', 'Schultz', 'Barker', 'Guzman', 'Page', 'Munoz', 'Ball', 'Keller', 'Chandler', 'Weber', 'Leonard', 'Walsh', 'Lyons', 'Ramsey', 'Wolfe', 'Schneider', 'Mullins', 'Benson', 'Sharp', 'Bowen', 'Daniel', 'Barber', 'Cummings', 'Hines', 'Baldwin', 'Griffith', 'Valdez', 'Hubbard', 'Salazar', 'Reeves', 'Warner', 'Stevenson', 'Burgess', 'Santos', 'Tate', 'Cross', 'Garner', 'Mann', 'Mack', 'Moss', 'Thornton', 'Dennis', 'McGee', 'Farmer', 'Delgado', 'Aguilar', 'Vega', 'Glover', 'Manning', 'Cohen', 'Harmon', 'Rodgers', 'Robbins', 'Newton', 'Todd', 'Blair', 'Higgins', 'Ingram', 'Reese', 'Cannon', 'Strickland', 'Townsend', 'Potter', 'Goodwin', 'Walton', 'Rowe', 'Hampton', 'Ortega', 'Patton', 'Swanson', 'Joseph', 'Francis', 'Goodman', 'Maldonado', 'Yates', 'Becker', 'Erickson', 'Hodges', 'Rios', 'Conner', 'Adkins', 'Webster', 'Norman', 'Malone', 'Hammond', 'Flowers', 'Cobb', 'Moody', 'Quinn', 'Blake', 'Maxwell', 'Pope', 'Floyd', 'Osborne', 'Paul', 'McCarthy', 'Guerrero', 'Lindsey', 'Estrada', 'Sandoval', 'Gibbs', 'Tyler', 'Gross', 'Fitzgerald', 'Stokes', 'Doyle', 'Sherman', 'Saunders', 'Wise', 'Colon', 'Gill', 'Alvarado', 'Greer', 'Padilla', 'Simon', 'Waters', 'Nunez', 'Ballard', 'Schwartz', 'McBride', 'Houston', 'Christensen', 'Klein', 'Pratt', 'Briggs', 'Parsons', 'McLaughlin', 'Zimmerman', 'French', 'Buchanan', 'Moran', 'Copeland', 'Roy', 'Pittman', 'Brady', 'McCormick', 'Holloway', 'Brock', 'Poole', 'Frank', 'Logan', 'Owen', 'Bass', 'Marsh', 'Drake', 'Wong', 'Jefferson', 'Park', 'Morton', 'Abbott', 'Sparks', 'Patrick', 'Norton', 'Huff', 'Clayton', 'Massey', 'Lloyd', 'Figueroa', 'Carson', 'Bowers', 'Roberson', 'Barton', 'Tran', 'Lamb', 'Harrington', 'Casey', 'Boone', 'Cortez', 'Clarke', 'Mathis', 'Singleton', 'Wilkins', 'Cain', 'Bryan', 'Underwood', 'Hogan', 'McKenzie', 'Collier', 'Luna', 'Phelps', 'McGuire', 'Allison', 'Bridges', 'Wilkerson', 'Nash', 'Summers', 'Atkins'], - // Data taken from http://www.dati.gov.it/dataset/comune-di-firenze_0164 (first 1000) - "it": ["Acciai", "Aglietti", "Agostini", "Agresti", "Ahmed", "Aiazzi", "Albanese", "Alberti", "Alessi", "Alfani", "Alinari", "Alterini", "Amato", "Ammannati", "Ancillotti", "Andrei", "Andreini", "Andreoni", "Angeli", "Anichini", "Antonelli", "Antonini", "Arena", "Ariani", "Arnetoli", "Arrighi", "Baccani", "Baccetti", "Bacci", "Bacherini", "Badii", "Baggiani", "Baglioni", "Bagni", "Bagnoli", "Baldassini", "Baldi", "Baldini", "Ballerini", "Balli", "Ballini", "Balloni", "Bambi", "Banchi", "Bandinelli", "Bandini", "Bani", "Barbetti", "Barbieri", "Barchielli", "Bardazzi", "Bardelli", "Bardi", "Barducci", "Bargellini", "Bargiacchi", "Barni", "Baroncelli", "Baroncini", "Barone", "Baroni", "Baronti", "Bartalesi", "Bartoletti", "Bartoli", "Bartolini", "Bartoloni", "Bartolozzi", "Basagni", "Basile", "Bassi", "Batacchi", "Battaglia", "Battaglini", "Bausi", "Becagli", "Becattini", "Becchi", "Becucci", "Bellandi", "Bellesi", "Belli", "Bellini", "Bellucci", "Bencini", "Benedetti", "Benelli", "Beni", "Benini", "Bensi", "Benucci", "Benvenuti", "Berlincioni", "Bernacchioni", "Bernardi", "Bernardini", "Berni", "Bernini", "Bertelli", "Berti", "Bertini", "Bessi", "Betti", "Bettini", "Biagi", "Biagini", "Biagioni", "Biagiotti", "Biancalani", "Bianchi", "Bianchini", "Bianco", "Biffoli", "Bigazzi", "Bigi", "Biliotti", "Billi", "Binazzi", "Bindi", "Bini", "Biondi", "Bizzarri", "Bocci", "Bogani", "Bolognesi", "Bonaiuti", "Bonanni", "Bonciani", "Boncinelli", "Bondi", "Bonechi", "Bongini", "Boni", "Bonini", "Borchi", "Boretti", "Borghi", "Borghini", "Borgioli", "Borri", "Borselli", "Boschi", "Bottai", "Bracci", "Braccini", "Brandi", "Braschi", "Bravi", "Brazzini", "Breschi", "Brilli", "Brizzi", "Brogelli", "Brogi", "Brogioni", "Brunelli", "Brunetti", "Bruni", "Bruno", "Brunori", "Bruschi", "Bucci", "Bucciarelli", "Buccioni", "Bucelli", "Bulli", "Burberi", "Burchi", "Burgassi", "Burroni", "Bussotti", "Buti", "Caciolli", "Caiani", "Calabrese", "Calamai", "Calamandrei", "Caldini", "Calo'", "Calonaci", "Calosi", "Calvelli", "Cambi", "Camiciottoli", "Cammelli", "Cammilli", "Campolmi", "Cantini", "Capanni", "Capecchi", "Caponi", "Cappelletti", "Cappelli", "Cappellini", "Cappugi", "Capretti", "Caputo", "Carbone", "Carboni", "Cardini", "Carlesi", "Carletti", "Carli", "Caroti", "Carotti", "Carrai", "Carraresi", "Carta", "Caruso", "Casalini", "Casati", "Caselli", "Casini", "Castagnoli", "Castellani", "Castelli", "Castellucci", "Catalano", "Catarzi", "Catelani", "Cavaciocchi", "Cavallaro", "Cavallini", "Cavicchi", "Cavini", "Ceccarelli", "Ceccatelli", "Ceccherelli", "Ceccherini", "Cecchi", "Cecchini", "Cecconi", "Cei", "Cellai", "Celli", "Cellini", "Cencetti", "Ceni", "Cenni", "Cerbai", "Cesari", "Ceseri", "Checcacci", "Checchi", "Checcucci", "Cheli", "Chellini", "Chen", "Cheng", "Cherici", "Cherubini", "Chiaramonti", "Chiarantini", "Chiarelli", "Chiari", "Chiarini", "Chiarugi", "Chiavacci", "Chiesi", "Chimenti", "Chini", "Chirici", "Chiti", "Ciabatti", "Ciampi", "Cianchi", "Cianfanelli", "Cianferoni", "Ciani", "Ciapetti", "Ciappi", "Ciardi", "Ciatti", "Cicali", "Ciccone", "Cinelli", "Cini", "Ciobanu", "Ciolli", "Cioni", "Cipriani", "Cirillo", "Cirri", "Ciucchi", "Ciuffi", "Ciulli", "Ciullini", "Clemente", "Cocchi", "Cognome", "Coli", "Collini", "Colombo", "Colzi", "Comparini", "Conforti", "Consigli", "Conte", "Conti", "Contini", "Coppini", "Coppola", "Corsi", "Corsini", "Corti", "Cortini", "Cosi", "Costa", "Costantini", "Costantino", "Cozzi", "Cresci", "Crescioli", "Cresti", "Crini", "Curradi", "D'Agostino", "D'Alessandro", "D'Amico", "D'Angelo", "Daddi", "Dainelli", "Dallai", "Danti", "Davitti", "De Angelis", "De Luca", "De Marco", "De Rosa", "De Santis", "De Simone", "De Vita", "Degl'Innocenti", "Degli Innocenti", "Dei", "Del Lungo", "Del Re", "Di Marco", "Di Stefano", "Dini", "Diop", "Dobre", "Dolfi", "Donati", "Dondoli", "Dong", "Donnini", "Ducci", "Dumitru", "Ermini", "Esposito", "Evangelisti", "Fabbri", "Fabbrini", "Fabbrizzi", "Fabbroni", "Fabbrucci", "Fabiani", "Facchini", "Faggi", "Fagioli", "Failli", "Faini", "Falciani", "Falcini", "Falcone", "Fallani", "Falorni", "Falsini", "Falugiani", "Fancelli", "Fanelli", "Fanetti", "Fanfani", "Fani", "Fantappie'", "Fantechi", "Fanti", "Fantini", "Fantoni", "Farina", "Fattori", "Favilli", "Fedi", "Fei", "Ferrante", "Ferrara", "Ferrari", "Ferraro", "Ferretti", "Ferri", "Ferrini", "Ferroni", "Fiaschi", "Fibbi", "Fiesoli", "Filippi", "Filippini", "Fini", "Fioravanti", "Fiore", "Fiorentini", "Fiorini", "Fissi", "Focardi", "Foggi", "Fontana", "Fontanelli", "Fontani", "Forconi", "Formigli", "Forte", "Forti", "Fortini", "Fossati", "Fossi", "Francalanci", "Franceschi", "Franceschini", "Franchi", "Franchini", "Franci", "Francini", "Francioni", "Franco", "Frassineti", "Frati", "Fratini", "Frilli", "Frizzi", "Frosali", "Frosini", "Frullini", "Fusco", "Fusi", "Gabbrielli", "Gabellini", "Gagliardi", "Galanti", "Galardi", "Galeotti", "Galletti", "Galli", "Gallo", "Gallori", "Gambacciani", "Gargani", "Garofalo", "Garuglieri", "Gashi", "Gasperini", "Gatti", "Gelli", "Gensini", "Gentile", "Gentili", "Geri", "Gerini", "Gheri", "Ghini", "Giachetti", "Giachi", "Giacomelli", "Gianassi", "Giani", "Giannelli", "Giannetti", "Gianni", "Giannini", "Giannoni", "Giannotti", "Giannozzi", "Gigli", "Giordano", "Giorgetti", "Giorgi", "Giovacchini", "Giovannelli", "Giovannetti", "Giovannini", "Giovannoni", "Giuliani", "Giunti", "Giuntini", "Giusti", "Gonnelli", "Goretti", "Gori", "Gradi", "Gramigni", "Grassi", "Grasso", "Graziani", "Grazzini", "Greco", "Grifoni", "Grillo", "Grimaldi", "Grossi", "Gualtieri", "Guarducci", "Guarino", "Guarnieri", "Guasti", "Guerra", "Guerri", "Guerrini", "Guidi", "Guidotti", "He", "Hoxha", "Hu", "Huang", "Iandelli", "Ignesti", "Innocenti", "Jin", "La Rosa", "Lai", "Landi", "Landini", "Lanini", "Lapi", "Lapini", "Lari", "Lascialfari", "Lastrucci", "Latini", "Lazzeri", "Lazzerini", "Lelli", "Lenzi", "Leonardi", "Leoncini", "Leone", "Leoni", "Lepri", "Li", "Liao", "Lin", "Linari", "Lippi", "Lisi", "Livi", "Lombardi", "Lombardini", "Lombardo", "Longo", "Lopez", "Lorenzi", "Lorenzini", "Lorini", "Lotti", "Lu", "Lucchesi", "Lucherini", "Lunghi", "Lupi", "Madiai", "Maestrini", "Maffei", "Maggi", "Maggini", "Magherini", "Magini", "Magnani", "Magnelli", "Magni", "Magnolfi", "Magrini", "Malavolti", "Malevolti", "Manca", "Mancini", "Manetti", "Manfredi", "Mangani", "Mannelli", "Manni", "Mannini", "Mannucci", "Manuelli", "Manzini", "Marcelli", "Marchese", "Marchetti", "Marchi", "Marchiani", "Marchionni", "Marconi", "Marcucci", "Margheri", "Mari", "Mariani", "Marilli", "Marinai", "Marinari", "Marinelli", "Marini", "Marino", "Mariotti", "Marsili", "Martelli", "Martinelli", "Martini", "Martino", "Marzi", "Masi", "Masini", "Masoni", "Massai", "Materassi", "Mattei", "Matteini", "Matteucci", "Matteuzzi", "Mattioli", "Mattolini", "Matucci", "Mauro", "Mazzanti", "Mazzei", "Mazzetti", "Mazzi", "Mazzini", "Mazzocchi", "Mazzoli", "Mazzoni", "Mazzuoli", "Meacci", "Mecocci", "Meini", "Melani", "Mele", "Meli", "Mengoni", "Menichetti", "Meoni", "Merlini", "Messeri", "Messina", "Meucci", "Miccinesi", "Miceli", "Micheli", "Michelini", "Michelozzi", "Migliori", "Migliorini", "Milani", "Miniati", "Misuri", "Monaco", "Montagnani", "Montagni", "Montanari", "Montelatici", "Monti", "Montigiani", "Montini", "Morandi", "Morandini", "Morelli", "Moretti", "Morganti", "Mori", "Morini", "Moroni", "Morozzi", "Mugnai", "Mugnaini", "Mustafa", "Naldi", "Naldini", "Nannelli", "Nanni", "Nannini", "Nannucci", "Nardi", "Nardini", "Nardoni", "Natali", "Ndiaye", "Nencetti", "Nencini", "Nencioni", "Neri", "Nesi", "Nesti", "Niccolai", "Niccoli", "Niccolini", "Nigi", "Nistri", "Nocentini", "Noferini", "Novelli", "Nucci", "Nuti", "Nutini", "Oliva", "Olivieri", "Olmi", "Orlandi", "Orlandini", "Orlando", "Orsini", "Ortolani", "Ottanelli", "Pacciani", "Pace", "Paci", "Pacini", "Pagani", "Pagano", "Paggetti", "Pagliai", "Pagni", "Pagnini", "Paladini", "Palagi", "Palchetti", "Palloni", "Palmieri", "Palumbo", "Pampaloni", "Pancani", "Pandolfi", "Pandolfini", "Panerai", "Panichi", "Paoletti", "Paoli", "Paolini", "Papi", "Papini", "Papucci", "Parenti", "Parigi", "Parisi", "Parri", "Parrini", "Pasquini", "Passeri", "Pecchioli", "Pecorini", "Pellegrini", "Pepi", "Perini", "Perrone", "Peruzzi", "Pesci", "Pestelli", "Petri", "Petrini", "Petrucci", "Pettini", "Pezzati", "Pezzatini", "Piani", "Piazza", "Piazzesi", "Piazzini", "Piccardi", "Picchi", "Piccini", "Piccioli", "Pieraccini", "Pieraccioni", "Pieralli", "Pierattini", "Pieri", "Pierini", "Pieroni", "Pietrini", "Pini", "Pinna", "Pinto", "Pinzani", "Pinzauti", "Piras", "Pisani", "Pistolesi", "Poggesi", "Poggi", "Poggiali", "Poggiolini", "Poli", "Pollastri", "Porciani", "Pozzi", "Pratellesi", "Pratesi", "Prosperi", "Pruneti", "Pucci", "Puccini", "Puccioni", "Pugi", "Pugliese", "Puliti", "Querci", "Quercioli", "Raddi", "Radu", "Raffaelli", "Ragazzini", "Ranfagni", "Ranieri", "Rastrelli", "Raugei", "Raveggi", "Renai", "Renzi", "Rettori", "Ricci", "Ricciardi", "Ridi", "Ridolfi", "Rigacci", "Righi", "Righini", "Rinaldi", "Risaliti", "Ristori", "Rizzo", "Rocchi", "Rocchini", "Rogai", "Romagnoli", "Romanelli", "Romani", "Romano", "Romei", "Romeo", "Romiti", "Romoli", "Romolini", "Rontini", "Rosati", "Roselli", "Rosi", "Rossetti", "Rossi", "Rossini", "Rovai", "Ruggeri", "Ruggiero", "Russo", "Sabatini", "Saccardi", "Sacchetti", "Sacchi", "Sacco", "Salerno", "Salimbeni", "Salucci", "Salvadori", "Salvestrini", "Salvi", "Salvini", "Sanesi", "Sani", "Sanna", "Santi", "Santini", "Santoni", "Santoro", "Santucci", "Sardi", "Sarri", "Sarti", "Sassi", "Sbolci", "Scali", "Scarpelli", "Scarselli", "Scopetani", "Secci", "Selvi", "Senatori", "Senesi", "Serafini", "Sereni", "Serra", "Sestini", "Sguanci", "Sieni", "Signorini", "Silvestri", "Simoncini", "Simonetti", "Simoni", "Singh", "Sodi", "Soldi", "Somigli", "Sorbi", "Sorelli", "Sorrentino", "Sottili", "Spina", "Spinelli", "Staccioli", "Staderini", "Stefanelli", "Stefani", "Stefanini", "Stella", "Susini", "Tacchi", "Tacconi", "Taddei", "Tagliaferri", "Tamburini", "Tanganelli", "Tani", "Tanini", "Tapinassi", "Tarchi", "Tarchiani", "Targioni", "Tassi", "Tassini", "Tempesti", "Terzani", "Tesi", "Testa", "Testi", "Tilli", "Tinti", "Tirinnanzi", "Toccafondi", "Tofanari", "Tofani", "Tognaccini", "Tonelli", "Tonini", "Torelli", "Torrini", "Tosi", "Toti", "Tozzi", "Trambusti", "Trapani", "Tucci", "Turchi", "Ugolini", "Ulivi", "Valente", "Valenti", "Valentini", "Vangelisti", "Vanni", "Vannini", "Vannoni", "Vannozzi", "Vannucchi", "Vannucci", "Ventura", "Venturi", "Venturini", "Vestri", "Vettori", "Vichi", "Viciani", "Vieri", "Vigiani", "Vignoli", "Vignolini", "Vignozzi", "Villani", "Vinci", "Visani", "Vitale", "Vitali", "Viti", "Viviani", "Vivoli", "Volpe", "Volpi", "Wang", "Wu", "Xu", "Yang", "Ye", "Zagli", "Zani", "Zanieri", "Zanobini", "Zecchi", "Zetti", "Zhang", "Zheng", "Zhou", "Zhu", "Zingoni", "Zini", "Zoppi"], - // http://www.voornamelijk.nl/meest-voorkomende-achternamen-in-nederland-en-amsterdam/ - "nl":["Albers", "Alblas", "Appelman", "Baars", "Baas", "Bakker", "Blank", "Bleeker", "Blok", "Blom", "Boer", "Boers", "Boldewijn", "Boon", "Boot", "Bos", "Bosch", "Bosma", "Bosman", "Bouma", "Bouman", "Bouwman", "Brands", "Brouwer", "Burger", "Buijs", "Buitenhuis", "Ceder", "Cohen", "Dekker", "Dekkers", "Dijkman", "Dijkstra", "Driessen", "Drost", "Engel", "Evers", "Faber", "Franke", "Gerritsen", "Goedhart", "Goossens", "Groen", "Groenenberg", "Groot", "Haan", "Hart", "Heemskerk", "Hendriks", "Hermans", "Hoekstra", "Hofman", "Hopman", "Huisman", "Jacobs", "Jansen", "Janssen", "Jonker", "Jaspers", "Keijzer", "Klaassen", "Klein", "Koek", "Koenders", "Kok", "Kool", "Koopman", "Koopmans", "Koning", "Koster", "Kramer", "Kroon", "Kuijpers", "Kuiper", "Kuipers", "Kurt", "Koster", "Kwakman", "Los", "Lubbers", "Maas", "Markus", "Martens", "Meijer", "Mol", "Molenaar", "Mulder", "Nieuwenhuis", "Peeters", "Peters", "Pengel", "Pieters", "Pool", "Post", "Postma", "Prins", "Pronk", "Reijnders", "Rietveld", "Roest", "Roos", "Sanders", "Schaap", "Scheffer", "Schenk", "Schilder", "Schipper", "Schmidt", "Scholten", "Schouten", "Schut", "Schutte", "Schuurman", "Simons", "Smeets", "Smit", "Smits", "Snel", "Swinkels", "Tas", "Terpstra", "Timmermans", "Tol", "Tromp", "Troost", "Valk", "Veenstra", "Veldkamp", "Verbeek", "Verheul", "Verhoeven", "Vermeer", "Vermeulen", "Verweij", "Vink", "Visser", "Voorn", "Vos", "Wagenaar", "Wiersema", "Willems", "Willemsen", "Witteveen", "Wolff", "Wolters", "Zijlstra", "Zwart", "de Beer", "de Boer", "de Bruijn", "de Bruin", "de Graaf", "de Groot", "de Haan", "de Haas", "de Jager", "de Jong", "de Jonge", "de Koning", "de Lange", "de Leeuw", "de Ridder", "de Rooij", "de Ruiter", "de Vos", "de Vries", "de Waal", "de Wit", "de Zwart", "van Beek", "van Boven", "van Dam", "van Dijk", "van Dongen", "van Doorn", "van Egmond", "van Eijk", "van Es", "van Gelder", "van Gelderen", "van Houten", "van Hulst", "van Kempen", "van Kesteren", "van Leeuwen", "van Loon", "van Mill", "van Noord", "van Ommen", "van Ommeren", "van Oosten", "van Oostveen", "van Rijn", "van Schaik", "van Veen", "van Vliet", "van Wijk", "van Wijngaarden", "van den Poel", "van de Pol", "van den Ploeg", "van de Ven", "van den Berg", "van den Bosch", "van den Brink", "van den Broek", "van den Heuvel", "van der Heijden", "van der Horst", "van der Hulst", "van der Kroon", "van der Laan", "van der Linden", "van der Meer", "van der Meij", "van der Meulen", "van der Molen", "van der Sluis", "van der Spek", "van der Veen", "van der Velde", "van der Velden", "van der Vliet", "van der Wal"], - // https://surnames.behindthename.com/top/lists/england-wales/1991 - "uk":["Smith","Jones","Williams","Taylor","Brown","Davies","Evans","Wilson","Thomas","Johnson","Roberts","Robinson","Thompson","Wright","Walker","White","Edwards","Hughes","Green","Hall","Lewis","Harris","Clarke","Patel","Jackson","Wood","Turner","Martin","Cooper","Hill","Ward","Morris","Moore","Clark","Lee","King","Baker","Harrison","Morgan","Allen","James","Scott","Phillips","Watson","Davis","Parker","Price","Bennett","Young","Griffiths","Mitchell","Kelly","Cook","Carter","Richardson","Bailey","Collins","Bell","Shaw","Murphy","Miller","Cox","Richards","Khan","Marshall","Anderson","Simpson","Ellis","Adams","Singh","Begum","Wilkinson","Foster","Chapman","Powell","Webb","Rogers","Gray","Mason","Ali","Hunt","Hussain","Campbell","Matthews","Owen","Palmer","Holmes","Mills","Barnes","Knight","Lloyd","Butler","Russell","Barker","Fisher","Stevens","Jenkins","Murray","Dixon","Harvey","Graham","Pearson","Ahmed","Fletcher","Walsh","Kaur","Gibson","Howard","Andrews","Stewart","Elliott","Reynolds","Saunders","Payne","Fox","Ford","Pearce","Day","Brooks","West","Lawrence","Cole","Atkinson","Bradley","Spencer","Gill","Dawson","Ball","Burton","O'brien","Watts","Rose","Booth","Perry","Ryan","Grant","Wells","Armstrong","Francis","Rees","Hayes","Hart","Hudson","Newman","Barrett","Webster","Hunter","Gregory","Carr","Lowe","Page","Marsh","Riley","Dunn","Woods","Parsons","Berry","Stone","Reid","Holland","Hawkins","Harding","Porter","Robertson","Newton","Oliver","Reed","Kennedy","Williamson","Bird","Gardner","Shah","Dean","Lane","Cooke","Bates","Henderson","Parry","Burgess","Bishop","Walton","Burns","Nicholson","Shepherd","Ross","Cross","Long","Freeman","Warren","Nicholls","Hamilton","Byrne","Sutton","Mcdonald","Yates","Hodgson","Robson","Curtis","Hopkins","O'connor","Harper","Coleman","Watkins","Moss","Mccarthy","Chambers","O'neill","Griffin","Sharp","Hardy","Wheeler","Potter","Osborne","Johnston","Gordon","Doyle","Wallace","George","Jordan","Hutchinson","Rowe","Burke","May","Pritchard","Gilbert","Willis","Higgins","Read","Miles","Stevenson","Stephenson","Hammond","Arnold","Buckley","Walters","Hewitt","Barber","Nelson","Slater","Austin","Sullivan","Whitehead","Mann","Frost","Lambert","Stephens","Blake","Akhtar","Lynch","Goodwin","Barton","Woodward","Thomson","Cunningham","Quinn","Barnett","Baxter","Bibi","Clayton","Nash","Greenwood","Jennings","Holt","Kemp","Poole","Gallagher","Bond","Stokes","Tucker","Davidson","Fowler","Heath","Norman","Middleton","Lawson","Banks","French","Stanley","Jarvis","Gibbs","Ferguson","Hayward","Carroll","Douglas","Dickinson","Todd","Barlow","Peters","Lucas","Knowles","Hartley","Miah","Simmons","Morton","Alexander","Field","Morrison","Norris","Townsend","Preston","Hancock","Thornton","Baldwin","Burrows","Briggs","Parkinson","Reeves","Macdonald","Lamb","Black","Abbott","Sanders","Thorpe","Holden","Tomlinson","Perkins","Ashton","Rhodes","Fuller","Howe","Bryant","Vaughan","Dale","Davey","Weston","Bartlett","Whittaker","Davison","Kent","Skinner","Birch","Morley","Daniels","Glover","Howell","Cartwright","Pugh","Humphreys","Goddard","Brennan","Wall","Kirby","Bowen","Savage","Bull","Wong","Dobson","Smart","Wilkins","Kirk","Fraser","Duffy","Hicks","Patterson","Bradshaw","Little","Archer","Warner","Waters","O'sullivan","Farrell","Brookes","Atkins","Kay","Dodd","Bentley","Flynn","John","Schofield","Short","Haynes","Wade","Butcher","Henry","Sanderson","Crawford","Sheppard","Bolton","Coates","Giles","Gould","Houghton","Gibbons","Pratt","Manning","Law","Hooper","Noble","Dyer","Rahman","Clements","Moran","Sykes","Chan","Doherty","Connolly","Joyce","Franklin","Hobbs","Coles","Herbert","Steele","Kerr","Leach","Winter","Owens","Duncan","Naylor","Fleming","Horton","Finch","Fitzgerald","Randall","Carpenter","Marsden","Browne","Garner","Pickering","Hale","Dennis","Vincent","Chadwick","Chandler","Sharpe","Nolan","Lyons","Hurst","Collier","Peacock","Howarth","Faulkner","Rice","Pollard","Welch","Norton","Gough","Sinclair","Blackburn","Bryan","Conway","Power","Cameron","Daly","Allan","Hanson","Gardiner","Boyle","Myers","Turnbull","Wallis","Mahmood","Sims","Swift","Iqbal","Pope","Brady","Chamberlain","Rowley","Tyler","Farmer","Metcalfe","Hilton","Godfrey","Holloway","Parkin","Bray","Talbot","Donnelly","Nixon","Charlton","Benson","Whitehouse","Barry","Hope","Lord","North","Storey","Connor","Potts","Bevan","Hargreaves","Mclean","Mistry","Bruce","Howells","Hyde","Parkes","Wyatt","Fry","Lees","O'donnell","Craig","Forster","Mckenzie","Humphries","Mellor","Carey","Ingram","Summers","Leonard"], - // https://surnames.behindthename.com/top/lists/germany/2017 - "de": ["Müller","Schmidt","Schneider","Fischer","Weber","Meyer","Wagner","Becker","Schulz","Hoffmann","Schäfer","Koch","Bauer","Richter","Klein","Wolf","Schröder","Neumann","Schwarz","Zimmermann","Braun","Krüger","Hofmann","Hartmann","Lange","Schmitt","Werner","Schmitz","Krause","Meier","Lehmann","Schmid","Schulze","Maier","Köhler","Herrmann","König","Walter","Mayer","Huber","Kaiser","Fuchs","Peters","Lang","Scholz","Möller","Weiß","Jung","Hahn","Schubert","Vogel","Friedrich","Keller","Günther","Frank","Berger","Winkler","Roth","Beck","Lorenz","Baumann","Franke","Albrecht","Schuster","Simon","Ludwig","Böhm","Winter","Kraus","Martin","Schumacher","Krämer","Vogt","Stein","Jäger","Otto","Sommer","Groß","Seidel","Heinrich","Brandt","Haas","Schreiber","Graf","Schulte","Dietrich","Ziegler","Kuhn","Kühn","Pohl","Engel","Horn","Busch","Bergmann","Thomas","Voigt","Sauer","Arnold","Wolff","Pfeiffer"], - // http://www.japantimes.co.jp/life/2009/10/11/lifestyle/japans-top-100-most-common-family-names/ - "jp": ["Sato","Suzuki","Takahashi","Tanaka","Watanabe","Ito","Yamamoto","Nakamura","Kobayashi","Kato","Yoshida","Yamada","Sasaki","Yamaguchi","Saito","Matsumoto","Inoue","Kimura","Hayashi","Shimizu","Yamazaki","Mori","Abe","Ikeda","Hashimoto","Yamashita","Ishikawa","Nakajima","Maeda","Fujita","Ogawa","Goto","Okada","Hasegawa","Murakami","Kondo","Ishii","Saito","Sakamoto","Endo","Aoki","Fujii","Nishimura","Fukuda","Ota","Miura","Fujiwara","Okamoto","Matsuda","Nakagawa","Nakano","Harada","Ono","Tamura","Takeuchi","Kaneko","Wada","Nakayama","Ishida","Ueda","Morita","Hara","Shibata","Sakai","Kudo","Yokoyama","Miyazaki","Miyamoto","Uchida","Takagi","Ando","Taniguchi","Ohno","Maruyama","Imai","Takada","Fujimoto","Takeda","Murata","Ueno","Sugiyama","Masuda","Sugawara","Hirano","Kojima","Otsuka","Chiba","Kubo","Matsui","Iwasaki","Sakurai","Kinoshita","Noguchi","Matsuo","Nomura","Kikuchi","Sano","Onishi","Sugimoto","Arai"], - // http://www.lowchensaustralia.com/names/popular-spanish-names.htm - "es": ["Garcia","Fernandez","Lopez","Martinez","Gonzalez","Rodriguez","Sanchez","Perez","Martin","Gomez","Ruiz","Diaz","Hernandez","Alvarez","Jimenez","Moreno","Munoz","Alonso","Romero","Navarro","Gutierrez","Torres","Dominguez","Gil","Vazquez","Blanco","Serrano","Ramos","Castro","Suarez","Sanz","Rubio","Ortega","Molina","Delgado","Ortiz","Morales","Ramirez","Marin","Iglesias","Santos","Castillo","Garrido","Calvo","Pena","Cruz","Cano","Nunez","Prieto","Diez","Lozano","Vidal","Pascual","Ferrer","Medina","Vega","Leon","Herrero","Vicente","Mendez","Guerrero","Fuentes","Campos","Nieto","Cortes","Caballero","Ibanez","Lorenzo","Pastor","Gimenez","Saez","Soler","Marquez","Carrasco","Herrera","Montero","Arias","Crespo","Flores","Andres","Aguilar","Hidalgo","Cabrera","Mora","Duran","Velasco","Rey","Pardo","Roman","Vila","Bravo","Merino","Moya","Soto","Izquierdo","Reyes","Redondo","Marcos","Carmona","Menendez"], - // Data taken from https://fr.wikipedia.org/wiki/Liste_des_noms_de_famille_les_plus_courants_en_France - "fr": ["Martin","Bernard","Thomas","Petit","Robert","Richard","Durand","Dubois","Moreau","Laurent","Simon","Michel","Lefèvre","Leroy","Roux","David","Bertrand","Morel","Fournier","Girard","Bonnet","Dupont","Lambert","Fontaine","Rousseau","Vincent","Müller","Lefèvre","Faure","André","Mercier","Blanc","Guérin","Boyer","Garnier","Chevalier","François","Legrand","Gauthier","Garcia","Perrin","Robin","Clément","Morin","Nicolas","Henry","Roussel","Matthieu","Gautier","Masson","Marchand","Duval","Denis","Dumont","Marie","Lemaire","Noël","Meyer","Dufour","Meunier","Brun","Blanchard","Giraud","Joly","Rivière","Lucas","Brunet","Gaillard","Barbier","Arnaud","Martínez","Gérard","Roche","Renard","Schmitt","Roy","Leroux","Colin","Vidal","Caron","Picard","Roger","Fabre","Aubert","Lemoine","Renaud","Dumas","Lacroix","Olivier","Philippe","Bourgeois","Pierre","Benoît","Rey","Leclerc","Payet","Rolland","Leclercq","Guillaume","Lecomte","López","Jean","Dupuy","Guillot","Hubert","Berger","Carpentier","Sánchez","Dupuis","Moulin","Louis","Deschamps","Huet","Vasseur","Perez","Boucher","Fleury","Royer","Klein","Jacquet","Adam","Paris","Poirier","Marty","Aubry","Guyot","Carré","Charles","Renault","Charpentier","Ménard","Maillard","Baron","Bertin","Bailly","Hervé","Schneider","Fernández","Le GallGall","Collet","Léger","Bouvier","Julien","Prévost","Millet","Perrot","Daniel","Le RouxRoux","Cousin","Germain","Breton","Besson","Langlois","Rémi","Le GoffGoff","Pelletier","Lévêque","Perrier","Leblanc","Barré","Lebrun","Marchal","Weber","Mallet","Hamon","Boulanger","Jacob","Monnier","Michaud","Rodríguez","Guichard","Gillet","Étienne","Grondin","Poulain","Tessier","Chevallier","Collin","Chauvin","Da SilvaSilva","Bouchet","Gay","Lemaître","Bénard","Maréchal","Humbert","Reynaud","Antoine","Hoarau","Perret","Barthélemy","Cordier","Pichon","Lejeune","Gilbert","Lamy","Delaunay","Pasquier","Carlier","LaporteLaporte"] - }, - - // Data taken from http://geoportal.statistics.gov.uk/datasets/ons-postcode-directory-latest-centroids - postcodeAreas: [{code: 'AB'}, {code: 'AL'}, {code: 'B'}, {code: 'BA'}, {code: 'BB'}, {code: 'BD'}, {code: 'BH'}, {code: 'BL'}, {code: 'BN'}, {code: 'BR'}, {code: 'BS'}, {code: 'BT'}, {code: 'CA'}, {code: 'CB'}, {code: 'CF'}, {code: 'CH'}, {code: 'CM'}, {code: 'CO'}, {code: 'CR'}, {code: 'CT'}, {code: 'CV'}, {code: 'CW'}, {code: 'DA'}, {code: 'DD'}, {code: 'DE'}, {code: 'DG'}, {code: 'DH'}, {code: 'DL'}, {code: 'DN'}, {code: 'DT'}, {code: 'DY'}, {code: 'E'}, {code: 'EC'}, {code: 'EH'}, {code: 'EN'}, {code: 'EX'}, {code: 'FK'}, {code: 'FY'}, {code: 'G'}, {code: 'GL'}, {code: 'GU'}, {code: 'GY'}, {code: 'HA'}, {code: 'HD'}, {code: 'HG'}, {code: 'HP'}, {code: 'HR'}, {code: 'HS'}, {code: 'HU'}, {code: 'HX'}, {code: 'IG'}, {code: 'IM'}, {code: 'IP'}, {code: 'IV'}, {code: 'JE'}, {code: 'KA'}, {code: 'KT'}, {code: 'KW'}, {code: 'KY'}, {code: 'L'}, {code: 'LA'}, {code: 'LD'}, {code: 'LE'}, {code: 'LL'}, {code: 'LN'}, {code: 'LS'}, {code: 'LU'}, {code: 'M'}, {code: 'ME'}, {code: 'MK'}, {code: 'ML'}, {code: 'N'}, {code: 'NE'}, {code: 'NG'}, {code: 'NN'}, {code: 'NP'}, {code: 'NR'}, {code: 'NW'}, {code: 'OL'}, {code: 'OX'}, {code: 'PA'}, {code: 'PE'}, {code: 'PH'}, {code: 'PL'}, {code: 'PO'}, {code: 'PR'}, {code: 'RG'}, {code: 'RH'}, {code: 'RM'}, {code: 'S'}, {code: 'SA'}, {code: 'SE'}, {code: 'SG'}, {code: 'SK'}, {code: 'SL'}, {code: 'SM'}, {code: 'SN'}, {code: 'SO'}, {code: 'SP'}, {code: 'SR'}, {code: 'SS'}, {code: 'ST'}, {code: 'SW'}, {code: 'SY'}, {code: 'TA'}, {code: 'TD'}, {code: 'TF'}, {code: 'TN'}, {code: 'TQ'}, {code: 'TR'}, {code: 'TS'}, {code: 'TW'}, {code: 'UB'}, {code: 'W'}, {code: 'WA'}, {code: 'WC'}, {code: 'WD'}, {code: 'WF'}, {code: 'WN'}, {code: 'WR'}, {code: 'WS'}, {code: 'WV'}, {code: 'YO'}, {code: 'ZE'}], - - // Data taken from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 - countries: [{"name":"Afghanistan","abbreviation":"AF"},{"name":"Åland Islands","abbreviation":"AX"},{"name":"Albania","abbreviation":"AL"},{"name":"Algeria","abbreviation":"DZ"},{"name":"American Samoa","abbreviation":"AS"},{"name":"Andorra","abbreviation":"AD"},{"name":"Angola","abbreviation":"AO"},{"name":"Anguilla","abbreviation":"AI"},{"name":"Antarctica","abbreviation":"AQ"},{"name":"Antigua and Barbuda","abbreviation":"AG"},{"name":"Argentina","abbreviation":"AR"},{"name":"Armenia","abbreviation":"AM"},{"name":"Aruba","abbreviation":"AW"},{"name":"Australia","abbreviation":"AU"},{"name":"Austria","abbreviation":"AT"},{"name":"Azerbaijan","abbreviation":"AZ"},{"name":"Bahamas","abbreviation":"BS"},{"name":"Bahrain","abbreviation":"BH"},{"name":"Bangladesh","abbreviation":"BD"},{"name":"Barbados","abbreviation":"BB"},{"name":"Belarus","abbreviation":"BY"},{"name":"Belgium","abbreviation":"BE"},{"name":"Belize","abbreviation":"BZ"},{"name":"Benin","abbreviation":"BJ"},{"name":"Bermuda","abbreviation":"BM"},{"name":"Bhutan","abbreviation":"BT"},{"name":"Plurinational State of Bolivia","abbreviation":"BO"},{"name":"Bonaire, Sint Eustatius and Saba","abbreviation":"BQ"},{"name":"Bosnia and Herzegovina","abbreviation":"BA"},{"name":"Botswana","abbreviation":"BW"},{"name":"Bouvet Island","abbreviation":"BV"},{"name":"Brazil","abbreviation":"BR"},{"name":"British Indian Ocean Territory","abbreviation":"IO"},{"name":"Brunei Darussalam","abbreviation":"BN"},{"name":"Bulgaria","abbreviation":"BG"},{"name":"Burkina Faso","abbreviation":"BF"},{"name":"Burundi","abbreviation":"BI"},{"name":"Cabo Verde","abbreviation":"CV"},{"name":"Cambodia","abbreviation":"KH"},{"name":"Cameroon","abbreviation":"CM"},{"name":"Canada","abbreviation":"CA"},{"name":"Cayman Islands","abbreviation":"KY"},{"name":"Central African Republic","abbreviation":"CF"},{"name":"Chad","abbreviation":"TD"},{"name":"Chile","abbreviation":"CL"},{"name":"China","abbreviation":"CN"},{"name":"Christmas Island","abbreviation":"CX"},{"name":"Cocos (Keeling) Islands","abbreviation":"CC"},{"name":"Colombia","abbreviation":"CO"},{"name":"Comoros","abbreviation":"KM"},{"name":"Congo","abbreviation":"CG"},{"name":"Democratic Republic of the Congo","abbreviation":"CD"},{"name":"Cook Islands","abbreviation":"CK"},{"name":"Costa Rica","abbreviation":"CR"},{"name":"Côte d'Ivoire","abbreviation":"CI"},{"name":"Croatia","abbreviation":"HR"},{"name":"Cuba","abbreviation":"CU"},{"name":"Curaçao","abbreviation":"CW"},{"name":"Cyprus","abbreviation":"CY"},{"name":"Czechia","abbreviation":"CZ"},{"name":"Denmark","abbreviation":"DK"},{"name":"Djibouti","abbreviation":"DJ"},{"name":"Dominica","abbreviation":"DM"},{"name":"Dominican Republic","abbreviation":"DO"},{"name":"Ecuador","abbreviation":"EC"},{"name":"Egypt","abbreviation":"EG"},{"name":"El Salvador","abbreviation":"SV"},{"name":"Equatorial Guinea","abbreviation":"GQ"},{"name":"Eritrea","abbreviation":"ER"},{"name":"Estonia","abbreviation":"EE"},{"name":"Eswatini","abbreviation":"SZ"},{"name":"Ethiopia","abbreviation":"ET"},{"name":"Falkland Islands (Malvinas)","abbreviation":"FK"},{"name":"Faroe Islands","abbreviation":"FO"},{"name":"Fiji","abbreviation":"FJ"},{"name":"Finland","abbreviation":"FI"},{"name":"France","abbreviation":"FR"},{"name":"French Guiana","abbreviation":"GF"},{"name":"French Polynesia","abbreviation":"PF"},{"name":"French Southern Territories","abbreviation":"TF"},{"name":"Gabon","abbreviation":"GA"},{"name":"Gambia","abbreviation":"GM"},{"name":"Georgia","abbreviation":"GE"},{"name":"Germany","abbreviation":"DE"},{"name":"Ghana","abbreviation":"GH"},{"name":"Gibraltar","abbreviation":"GI"},{"name":"Greece","abbreviation":"GR"},{"name":"Greenland","abbreviation":"GL"},{"name":"Grenada","abbreviation":"GD"},{"name":"Guadeloupe","abbreviation":"GP"},{"name":"Guam","abbreviation":"GU"},{"name":"Guatemala","abbreviation":"GT"},{"name":"Guernsey","abbreviation":"GG"},{"name":"Guinea","abbreviation":"GN"},{"name":"Guinea-Bissau","abbreviation":"GW"},{"name":"Guyana","abbreviation":"GY"},{"name":"Haiti","abbreviation":"HT"},{"name":"Heard Island and McDonald Islands","abbreviation":"HM"},{"name":"Holy See","abbreviation":"VA"},{"name":"Honduras","abbreviation":"HN"},{"name":"Hong Kong","abbreviation":"HK"},{"name":"Hungary","abbreviation":"HU"},{"name":"Iceland","abbreviation":"IS"},{"name":"India","abbreviation":"IN"},{"name":"Indonesia","abbreviation":"ID"},{"name":"Islamic Republic of Iran","abbreviation":"IR"},{"name":"Iraq","abbreviation":"IQ"},{"name":"Ireland","abbreviation":"IE"},{"name":"Isle of Man","abbreviation":"IM"},{"name":"Israel","abbreviation":"IL"},{"name":"Italy","abbreviation":"IT"},{"name":"Jamaica","abbreviation":"JM"},{"name":"Japan","abbreviation":"JP"},{"name":"Jersey","abbreviation":"JE"},{"name":"Jordan","abbreviation":"JO"},{"name":"Kazakhstan","abbreviation":"KZ"},{"name":"Kenya","abbreviation":"KE"},{"name":"Kiribati","abbreviation":"KI"},{"name":"Democratic People's Republic of Korea","abbreviation":"KP"},{"name":"Republic of Korea","abbreviation":"KR"},{"name":"Kuwait","abbreviation":"KW"},{"name":"Kyrgyzstan","abbreviation":"KG"},{"name":"Lao People's Democratic Republic","abbreviation":"LA"},{"name":"Latvia","abbreviation":"LV"},{"name":"Lebanon","abbreviation":"LB"},{"name":"Lesotho","abbreviation":"LS"},{"name":"Liberia","abbreviation":"LR"},{"name":"Libya","abbreviation":"LY"},{"name":"Liechtenstein","abbreviation":"LI"},{"name":"Lithuania","abbreviation":"LT"},{"name":"Luxembourg","abbreviation":"LU"},{"name":"Macao","abbreviation":"MO"},{"name":"Madagascar","abbreviation":"MG"},{"name":"Malawi","abbreviation":"MW"},{"name":"Malaysia","abbreviation":"MY"},{"name":"Maldives","abbreviation":"MV"},{"name":"Mali","abbreviation":"ML"},{"name":"Malta","abbreviation":"MT"},{"name":"Marshall Islands","abbreviation":"MH"},{"name":"Martinique","abbreviation":"MQ"},{"name":"Mauritania","abbreviation":"MR"},{"name":"Mauritius","abbreviation":"MU"},{"name":"Mayotte","abbreviation":"YT"},{"name":"Mexico","abbreviation":"MX"},{"name":"Federated States of Micronesia","abbreviation":"FM"},{"name":"Republic of Moldova","abbreviation":"MD"},{"name":"Monaco","abbreviation":"MC"},{"name":"Mongolia","abbreviation":"MN"},{"name":"Montenegro","abbreviation":"ME"},{"name":"Montserrat","abbreviation":"MS"},{"name":"Morocco","abbreviation":"MA"},{"name":"Mozambique","abbreviation":"MZ"},{"name":"Myanmar","abbreviation":"MM"},{"name":"Namibia","abbreviation":"NA"},{"name":"Nauru","abbreviation":"NR"},{"name":"Nepal","abbreviation":"NP"},{"name":"Kingdom of the Netherlands","abbreviation":"NL"},{"name":"New Caledonia","abbreviation":"NC"},{"name":"New Zealand","abbreviation":"NZ"},{"name":"Nicaragua","abbreviation":"NI"},{"name":"Niger","abbreviation":"NE"},{"name":"Nigeria","abbreviation":"NG"},{"name":"Niue","abbreviation":"NU"},{"name":"Norfolk Island","abbreviation":"NF"},{"name":"North Macedonia","abbreviation":"MK"},{"name":"Northern Mariana Islands","abbreviation":"MP"},{"name":"Norway","abbreviation":"NO"},{"name":"Oman","abbreviation":"OM"},{"name":"Pakistan","abbreviation":"PK"},{"name":"Palau","abbreviation":"PW"},{"name":"State of Palestine","abbreviation":"PS"},{"name":"Panama","abbreviation":"PA"},{"name":"Papua New Guinea","abbreviation":"PG"},{"name":"Paraguay","abbreviation":"PY"},{"name":"Peru","abbreviation":"PE"},{"name":"Philippines","abbreviation":"PH"},{"name":"Pitcairn","abbreviation":"PN"},{"name":"Poland","abbreviation":"PL"},{"name":"Portugal","abbreviation":"PT"},{"name":"Puerto Rico","abbreviation":"PR"},{"name":"Qatar","abbreviation":"QA"},{"name":"Réunion","abbreviation":"RE"},{"name":"Romania","abbreviation":"RO"},{"name":"Russian Federation","abbreviation":"RU"},{"name":"Rwanda","abbreviation":"RW"},{"name":"Saint Barthélemy","abbreviation":"BL"},{"name":"Saint Helena, Ascension and Tristan da Cunha","abbreviation":"SH"},{"name":"Saint Kitts and Nevis","abbreviation":"KN"},{"name":"Saint Lucia","abbreviation":"LC"},{"name":"Saint Martin (French part)","abbreviation":"MF"},{"name":"Saint Pierre and Miquelon","abbreviation":"PM"},{"name":"Saint Vincent and the Grenadines","abbreviation":"VC"},{"name":"Samoa","abbreviation":"WS"},{"name":"San Marino","abbreviation":"SM"},{"name":"Sao Tome and Principe","abbreviation":"ST"},{"name":"Saudi Arabia","abbreviation":"SA"},{"name":"Senegal","abbreviation":"SN"},{"name":"Serbia","abbreviation":"RS"},{"name":"Seychelles","abbreviation":"SC"},{"name":"Sierra Leone","abbreviation":"SL"},{"name":"Singapore","abbreviation":"SG"},{"name":"Sint Maarten (Dutch part)","abbreviation":"SX"},{"name":"Slovakia","abbreviation":"SK"},{"name":"Slovenia","abbreviation":"SI"},{"name":"Solomon Islands","abbreviation":"SB"},{"name":"Somalia","abbreviation":"SO"},{"name":"South Africa","abbreviation":"ZA"},{"name":"South Georgia and the South Sandwich Islands","abbreviation":"GS"},{"name":"South Sudan","abbreviation":"SS"},{"name":"Spain","abbreviation":"ES"},{"name":"Sri Lanka","abbreviation":"LK"},{"name":"Sudan","abbreviation":"SD"},{"name":"Suriname","abbreviation":"SR"},{"name":"Svalbard and Jan Mayen","abbreviation":"SJ"},{"name":"Sweden","abbreviation":"SE"},{"name":"Switzerland","abbreviation":"CH"},{"name":"Syrian Arab Republic","abbreviation":"SY"},{"name":"Taiwan, Province of China","abbreviation":"TW"},{"name":"Tajikistan","abbreviation":"TJ"},{"name":"United Republic of Tanzania","abbreviation":"TZ"},{"name":"Thailand","abbreviation":"TH"},{"name":"Timor-Leste","abbreviation":"TL"},{"name":"Togo","abbreviation":"TG"},{"name":"Tokelau","abbreviation":"TK"},{"name":"Tonga","abbreviation":"TO"},{"name":"Trinidad and Tobago","abbreviation":"TT"},{"name":"Tunisia","abbreviation":"TN"},{"name":"Türkiye","abbreviation":"TR"},{"name":"Turkmenistan","abbreviation":"TM"},{"name":"Turks and Caicos Islands","abbreviation":"TC"},{"name":"Tuvalu","abbreviation":"TV"},{"name":"Uganda","abbreviation":"UG"},{"name":"Ukraine","abbreviation":"UA"},{"name":"United Arab Emirates","abbreviation":"AE"},{"name":"United Kingdom of Great Britain and Northern Ireland","abbreviation":"GB"},{"name":"United States Minor Outlying Islands","abbreviation":"UM"},{"name":"United States of America","abbreviation":"US"},{"name":"Uruguay","abbreviation":"UY"},{"name":"Uzbekistan","abbreviation":"UZ"},{"name":"Vanuatu","abbreviation":"VU"},{"name":"Bolivarian Republic of Venezuela","abbreviation":"VE"},{"name":"Viet Nam","abbreviation":"VN"},{"name":"Virgin Islands (British)","abbreviation":"VG"},{"name":"Virgin Islands (U.S.)","abbreviation":"VI"},{"name":"Wallis and Futuna","abbreviation":"WF"},{"name":"Western Sahara","abbreviation":"EH"},{"name":"Yemen","abbreviation":"YE"},{"name":"Zambia","abbreviation":"ZM"},{"name":"Zimbabwe","abbreviation":"ZW"}], - - counties: { - // Data taken from http://www.downloadexcelfiles.com/gb_en/download-excel-file-list-counties-uk - "uk": [ - {name: 'Bath and North East Somerset'}, - {name: 'Aberdeenshire'}, - {name: 'Anglesey'}, - {name: 'Angus'}, - {name: 'Bedford'}, - {name: 'Blackburn with Darwen'}, - {name: 'Blackpool'}, - {name: 'Bournemouth'}, - {name: 'Bracknell Forest'}, - {name: 'Brighton & Hove'}, - {name: 'Bristol'}, - {name: 'Buckinghamshire'}, - {name: 'Cambridgeshire'}, - {name: 'Carmarthenshire'}, - {name: 'Central Bedfordshire'}, - {name: 'Ceredigion'}, - {name: 'Cheshire East'}, - {name: 'Cheshire West and Chester'}, - {name: 'Clackmannanshire'}, - {name: 'Conwy'}, - {name: 'Cornwall'}, - {name: 'County Antrim'}, - {name: 'County Armagh'}, - {name: 'County Down'}, - {name: 'County Durham'}, - {name: 'County Fermanagh'}, - {name: 'County Londonderry'}, - {name: 'County Tyrone'}, - {name: 'Cumbria'}, - {name: 'Darlington'}, - {name: 'Denbighshire'}, - {name: 'Derby'}, - {name: 'Derbyshire'}, - {name: 'Devon'}, - {name: 'Dorset'}, - {name: 'Dumfries and Galloway'}, - {name: 'Dundee'}, - {name: 'East Lothian'}, - {name: 'East Riding of Yorkshire'}, - {name: 'East Sussex'}, - {name: 'Edinburgh?'}, - {name: 'Essex'}, - {name: 'Falkirk'}, - {name: 'Fife'}, - {name: 'Flintshire'}, - {name: 'Gloucestershire'}, - {name: 'Greater London'}, - {name: 'Greater Manchester'}, - {name: 'Gwent'}, - {name: 'Gwynedd'}, - {name: 'Halton'}, - {name: 'Hampshire'}, - {name: 'Hartlepool'}, - {name: 'Herefordshire'}, - {name: 'Hertfordshire'}, - {name: 'Highlands'}, - {name: 'Hull'}, - {name: 'Isle of Wight'}, - {name: 'Isles of Scilly'}, - {name: 'Kent'}, - {name: 'Lancashire'}, - {name: 'Leicester'}, - {name: 'Leicestershire'}, - {name: 'Lincolnshire'}, - {name: 'Lothian'}, - {name: 'Luton'}, - {name: 'Medway'}, - {name: 'Merseyside'}, - {name: 'Mid Glamorgan'}, - {name: 'Middlesbrough'}, - {name: 'Milton Keynes'}, - {name: 'Monmouthshire'}, - {name: 'Moray'}, - {name: 'Norfolk'}, - {name: 'North East Lincolnshire'}, - {name: 'North Lincolnshire'}, - {name: 'North Somerset'}, - {name: 'North Yorkshire'}, - {name: 'Northamptonshire'}, - {name: 'Northumberland'}, - {name: 'Nottingham'}, - {name: 'Nottinghamshire'}, - {name: 'Oxfordshire'}, - {name: 'Pembrokeshire'}, - {name: 'Perth and Kinross'}, - {name: 'Peterborough'}, - {name: 'Plymouth'}, - {name: 'Poole'}, - {name: 'Portsmouth'}, - {name: 'Powys'}, - {name: 'Reading'}, - {name: 'Redcar and Cleveland'}, - {name: 'Rutland'}, - {name: 'Scottish Borders'}, - {name: 'Shropshire'}, - {name: 'Slough'}, - {name: 'Somerset'}, - {name: 'South Glamorgan'}, - {name: 'South Gloucestershire'}, - {name: 'South Yorkshire'}, - {name: 'Southampton'}, - {name: 'Southend-on-Sea'}, - {name: 'Staffordshire'}, - {name: 'Stirlingshire'}, - {name: 'Stockton-on-Tees'}, - {name: 'Stoke-on-Trent'}, - {name: 'Strathclyde'}, - {name: 'Suffolk'}, - {name: 'Surrey'}, - {name: 'Swindon'}, - {name: 'Telford and Wrekin'}, - {name: 'Thurrock'}, - {name: 'Torbay'}, - {name: 'Tyne and Wear'}, - {name: 'Warrington'}, - {name: 'Warwickshire'}, - {name: 'West Berkshire'}, - {name: 'West Glamorgan'}, - {name: 'West Lothian'}, - {name: 'West Midlands'}, - {name: 'West Sussex'}, - {name: 'West Yorkshire'}, - {name: 'Western Isles'}, - {name: 'Wiltshire'}, - {name: 'Windsor and Maidenhead'}, - {name: 'Wokingham'}, - {name: 'Worcestershire'}, - {name: 'Wrexham'}, - {name: 'York'}] - }, - provinces: { - "ca": [ - {name: 'Alberta', abbreviation: 'AB'}, - {name: 'British Columbia', abbreviation: 'BC'}, - {name: 'Manitoba', abbreviation: 'MB'}, - {name: 'New Brunswick', abbreviation: 'NB'}, - {name: 'Newfoundland and Labrador', abbreviation: 'NL'}, - {name: 'Nova Scotia', abbreviation: 'NS'}, - {name: 'Ontario', abbreviation: 'ON'}, - {name: 'Prince Edward Island', abbreviation: 'PE'}, - {name: 'Quebec', abbreviation: 'QC'}, - {name: 'Saskatchewan', abbreviation: 'SK'}, - - // The case could be made that the following are not actually provinces - // since they are technically considered "territories" however they all - // look the same on an envelope! - {name: 'Northwest Territories', abbreviation: 'NT'}, - {name: 'Nunavut', abbreviation: 'NU'}, - {name: 'Yukon', abbreviation: 'YT'} - ], - "it": [ - { name: "Agrigento", abbreviation: "AG", code: 84 }, - { name: "Alessandria", abbreviation: "AL", code: 6 }, - { name: "Ancona", abbreviation: "AN", code: 42 }, - { name: "Aosta", abbreviation: "AO", code: 7 }, - { name: "L'Aquila", abbreviation: "AQ", code: 66 }, - { name: "Arezzo", abbreviation: "AR", code: 51 }, - { name: "Ascoli-Piceno", abbreviation: "AP", code: 44 }, - { name: "Asti", abbreviation: "AT", code: 5 }, - { name: "Avellino", abbreviation: "AV", code: 64 }, - { name: "Bari", abbreviation: "BA", code: 72 }, - { name: "Barletta-Andria-Trani", abbreviation: "BT", code: 72 }, - { name: "Belluno", abbreviation: "BL", code: 25 }, - { name: "Benevento", abbreviation: "BN", code: 62 }, - { name: "Bergamo", abbreviation: "BG", code: 16 }, - { name: "Biella", abbreviation: "BI", code: 96 }, - { name: "Bologna", abbreviation: "BO", code: 37 }, - { name: "Bolzano", abbreviation: "BZ", code: 21 }, - { name: "Brescia", abbreviation: "BS", code: 17 }, - { name: "Brindisi", abbreviation: "BR", code: 74 }, - { name: "Cagliari", abbreviation: "CA", code: 92 }, - { name: "Caltanissetta", abbreviation: "CL", code: 85 }, - { name: "Campobasso", abbreviation: "CB", code: 70 }, - { name: "Carbonia Iglesias", abbreviation: "CI", code: 70 }, - { name: "Caserta", abbreviation: "CE", code: 61 }, - { name: "Catania", abbreviation: "CT", code: 87 }, - { name: "Catanzaro", abbreviation: "CZ", code: 79 }, - { name: "Chieti", abbreviation: "CH", code: 69 }, - { name: "Como", abbreviation: "CO", code: 13 }, - { name: "Cosenza", abbreviation: "CS", code: 78 }, - { name: "Cremona", abbreviation: "CR", code: 19 }, - { name: "Crotone", abbreviation: "KR", code: 101 }, - { name: "Cuneo", abbreviation: "CN", code: 4 }, - { name: "Enna", abbreviation: "EN", code: 86 }, - { name: "Fermo", abbreviation: "FM", code: 86 }, - { name: "Ferrara", abbreviation: "FE", code: 38 }, - { name: "Firenze", abbreviation: "FI", code: 48 }, - { name: "Foggia", abbreviation: "FG", code: 71 }, - { name: "Forli-Cesena", abbreviation: "FC", code: 71 }, - { name: "Frosinone", abbreviation: "FR", code: 60 }, - { name: "Genova", abbreviation: "GE", code: 10 }, - { name: "Gorizia", abbreviation: "GO", code: 31 }, - { name: "Grosseto", abbreviation: "GR", code: 53 }, - { name: "Imperia", abbreviation: "IM", code: 8 }, - { name: "Isernia", abbreviation: "IS", code: 94 }, - { name: "La-Spezia", abbreviation: "SP", code: 66 }, - { name: "Latina", abbreviation: "LT", code: 59 }, - { name: "Lecce", abbreviation: "LE", code: 75 }, - { name: "Lecco", abbreviation: "LC", code: 97 }, - { name: "Livorno", abbreviation: "LI", code: 49 }, - { name: "Lodi", abbreviation: "LO", code: 98 }, - { name: "Lucca", abbreviation: "LU", code: 46 }, - { name: "Macerata", abbreviation: "MC", code: 43 }, - { name: "Mantova", abbreviation: "MN", code: 20 }, - { name: "Massa-Carrara", abbreviation: "MS", code: 45 }, - { name: "Matera", abbreviation: "MT", code: 77 }, - { name: "Medio Campidano", abbreviation: "VS", code: 77 }, - { name: "Messina", abbreviation: "ME", code: 83 }, - { name: "Milano", abbreviation: "MI", code: 15 }, - { name: "Modena", abbreviation: "MO", code: 36 }, - { name: "Monza-Brianza", abbreviation: "MB", code: 36 }, - { name: "Napoli", abbreviation: "NA", code: 63 }, - { name: "Novara", abbreviation: "NO", code: 3 }, - { name: "Nuoro", abbreviation: "NU", code: 91 }, - { name: "Ogliastra", abbreviation: "OG", code: 91 }, - { name: "Olbia Tempio", abbreviation: "OT", code: 91 }, - { name: "Oristano", abbreviation: "OR", code: 95 }, - { name: "Padova", abbreviation: "PD", code: 28 }, - { name: "Palermo", abbreviation: "PA", code: 82 }, - { name: "Parma", abbreviation: "PR", code: 34 }, - { name: "Pavia", abbreviation: "PV", code: 18 }, - { name: "Perugia", abbreviation: "PG", code: 54 }, - { name: "Pesaro-Urbino", abbreviation: "PU", code: 41 }, - { name: "Pescara", abbreviation: "PE", code: 68 }, - { name: "Piacenza", abbreviation: "PC", code: 33 }, - { name: "Pisa", abbreviation: "PI", code: 50 }, - { name: "Pistoia", abbreviation: "PT", code: 47 }, - { name: "Pordenone", abbreviation: "PN", code: 93 }, - { name: "Potenza", abbreviation: "PZ", code: 76 }, - { name: "Prato", abbreviation: "PO", code: 100 }, - { name: "Ragusa", abbreviation: "RG", code: 88 }, - { name: "Ravenna", abbreviation: "RA", code: 39 }, - { name: "Reggio-Calabria", abbreviation: "RC", code: 35 }, - { name: "Reggio-Emilia", abbreviation: "RE", code: 35 }, - { name: "Rieti", abbreviation: "RI", code: 57 }, - { name: "Rimini", abbreviation: "RN", code: 99 }, - { name: "Roma", abbreviation: "Roma", code: 58 }, - { name: "Rovigo", abbreviation: "RO", code: 29 }, - { name: "Salerno", abbreviation: "SA", code: 65 }, - { name: "Sassari", abbreviation: "SS", code: 90 }, - { name: "Savona", abbreviation: "SV", code: 9 }, - { name: "Siena", abbreviation: "SI", code: 52 }, - { name: "Siracusa", abbreviation: "SR", code: 89 }, - { name: "Sondrio", abbreviation: "SO", code: 14 }, - { name: "Taranto", abbreviation: "TA", code: 73 }, - { name: "Teramo", abbreviation: "TE", code: 67 }, - { name: "Terni", abbreviation: "TR", code: 55 }, - { name: "Torino", abbreviation: "TO", code: 1 }, - { name: "Trapani", abbreviation: "TP", code: 81 }, - { name: "Trento", abbreviation: "TN", code: 22 }, - { name: "Treviso", abbreviation: "TV", code: 26 }, - { name: "Trieste", abbreviation: "TS", code: 32 }, - { name: "Udine", abbreviation: "UD", code: 30 }, - { name: "Varese", abbreviation: "VA", code: 12 }, - { name: "Venezia", abbreviation: "VE", code: 27 }, - { name: "Verbania", abbreviation: "VB", code: 27 }, - { name: "Vercelli", abbreviation: "VC", code: 2 }, - { name: "Verona", abbreviation: "VR", code: 23 }, - { name: "Vibo-Valentia", abbreviation: "VV", code: 102 }, - { name: "Vicenza", abbreviation: "VI", code: 24 }, - { name: "Viterbo", abbreviation: "VT", code: 56 } - ] - }, - - // from: https://github.com/samsargent/Useful-Autocomplete-Data/blob/master/data/nationalities.json - nationalities: [ - {name: 'Afghan'}, - {name: 'Albanian'}, - {name: 'Algerian'}, - {name: 'American'}, - {name: 'Andorran'}, - {name: 'Angolan'}, - {name: 'Antiguans'}, - {name: 'Argentinean'}, - {name: 'Armenian'}, - {name: 'Australian'}, - {name: 'Austrian'}, - {name: 'Azerbaijani'}, - {name: 'Bahami'}, - {name: 'Bahraini'}, - {name: 'Bangladeshi'}, - {name: 'Barbadian'}, - {name: 'Barbudans'}, - {name: 'Batswana'}, - {name: 'Belarusian'}, - {name: 'Belgian'}, - {name: 'Belizean'}, - {name: 'Beninese'}, - {name: 'Bhutanese'}, - {name: 'Bolivian'}, - {name: 'Bosnian'}, - {name: 'Brazilian'}, - {name: 'British'}, - {name: 'Bruneian'}, - {name: 'Bulgarian'}, - {name: 'Burkinabe'}, - {name: 'Burmese'}, - {name: 'Burundian'}, - {name: 'Cambodian'}, - {name: 'Cameroonian'}, - {name: 'Canadian'}, - {name: 'Cape Verdean'}, - {name: 'Central African'}, - {name: 'Chadian'}, - {name: 'Chilean'}, - {name: 'Chinese'}, - {name: 'Colombian'}, - {name: 'Comoran'}, - {name: 'Congolese'}, - {name: 'Costa Rican'}, - {name: 'Croatian'}, - {name: 'Cuban'}, - {name: 'Cypriot'}, - {name: 'Czech'}, - {name: 'Danish'}, - {name: 'Djibouti'}, - {name: 'Dominican'}, - {name: 'Dutch'}, - {name: 'East Timorese'}, - {name: 'Ecuadorean'}, - {name: 'Egyptian'}, - {name: 'Emirian'}, - {name: 'Equatorial Guinean'}, - {name: 'Eritrean'}, - {name: 'Estonian'}, - {name: 'Ethiopian'}, - {name: 'Fijian'}, - {name: 'Filipino'}, - {name: 'Finnish'}, - {name: 'French'}, - {name: 'Gabonese'}, - {name: 'Gambian'}, - {name: 'Georgian'}, - {name: 'German'}, - {name: 'Ghanaian'}, - {name: 'Greek'}, - {name: 'Grenadian'}, - {name: 'Guatemalan'}, - {name: 'Guinea-Bissauan'}, - {name: 'Guinean'}, - {name: 'Guyanese'}, - {name: 'Haitian'}, - {name: 'Herzegovinian'}, - {name: 'Honduran'}, - {name: 'Hungarian'}, - {name: 'I-Kiribati'}, - {name: 'Icelander'}, - {name: 'Indian'}, - {name: 'Indonesian'}, - {name: 'Iranian'}, - {name: 'Iraqi'}, - {name: 'Irish'}, - {name: 'Israeli'}, - {name: 'Italian'}, - {name: 'Ivorian'}, - {name: 'Jamaican'}, - {name: 'Japanese'}, - {name: 'Jordanian'}, - {name: 'Kazakhstani'}, - {name: 'Kenyan'}, - {name: 'Kittian and Nevisian'}, - {name: 'Kuwaiti'}, - {name: 'Kyrgyz'}, - {name: 'Laotian'}, - {name: 'Latvian'}, - {name: 'Lebanese'}, - {name: 'Liberian'}, - {name: 'Libyan'}, - {name: 'Liechtensteiner'}, - {name: 'Lithuanian'}, - {name: 'Luxembourger'}, - {name: 'Macedonian'}, - {name: 'Malagasy'}, - {name: 'Malawian'}, - {name: 'Malaysian'}, - {name: 'Maldivan'}, - {name: 'Malian'}, - {name: 'Maltese'}, - {name: 'Marshallese'}, - {name: 'Mauritanian'}, - {name: 'Mauritian'}, - {name: 'Mexican'}, - {name: 'Micronesian'}, - {name: 'Moldovan'}, - {name: 'Monacan'}, - {name: 'Mongolian'}, - {name: 'Moroccan'}, - {name: 'Mosotho'}, - {name: 'Motswana'}, - {name: 'Mozambican'}, - {name: 'Namibian'}, - {name: 'Nauruan'}, - {name: 'Nepalese'}, - {name: 'New Zealander'}, - {name: 'Nicaraguan'}, - {name: 'Nigerian'}, - {name: 'Nigerien'}, - {name: 'North Korean'}, - {name: 'Northern Irish'}, - {name: 'Norwegian'}, - {name: 'Omani'}, - {name: 'Pakistani'}, - {name: 'Palauan'}, - {name: 'Panamanian'}, - {name: 'Papua New Guinean'}, - {name: 'Paraguayan'}, - {name: 'Peruvian'}, - {name: 'Polish'}, - {name: 'Portuguese'}, - {name: 'Qatari'}, - {name: 'Romani'}, - {name: 'Russian'}, - {name: 'Rwandan'}, - {name: 'Saint Lucian'}, - {name: 'Salvadoran'}, - {name: 'Samoan'}, - {name: 'San Marinese'}, - {name: 'Sao Tomean'}, - {name: 'Saudi'}, - {name: 'Scottish'}, - {name: 'Senegalese'}, - {name: 'Serbian'}, - {name: 'Seychellois'}, - {name: 'Sierra Leonean'}, - {name: 'Singaporean'}, - {name: 'Slovakian'}, - {name: 'Slovenian'}, - {name: 'Solomon Islander'}, - {name: 'Somali'}, - {name: 'South African'}, - {name: 'South Korean'}, - {name: 'Spanish'}, - {name: 'Sri Lankan'}, - {name: 'Sudanese'}, - {name: 'Surinamer'}, - {name: 'Swazi'}, - {name: 'Swedish'}, - {name: 'Swiss'}, - {name: 'Syrian'}, - {name: 'Taiwanese'}, - {name: 'Tajik'}, - {name: 'Tanzanian'}, - {name: 'Thai'}, - {name: 'Togolese'}, - {name: 'Tongan'}, - {name: 'Trinidadian or Tobagonian'}, - {name: 'Tunisian'}, - {name: 'Turkish'}, - {name: 'Tuvaluan'}, - {name: 'Ugandan'}, - {name: 'Ukrainian'}, - {name: 'Uruguaya'}, - {name: 'Uzbekistani'}, - {name: 'Venezuela'}, - {name: 'Vietnamese'}, - {name: 'Wels'}, - {name: 'Yemenit'}, - {name: 'Zambia'}, - {name: 'Zimbabwe'}, - ], - // http://www.loc.gov/standards/iso639-2/php/code_list.php (ISO-639-1 codes) - locale_languages: [ - "aa", - "ab", - "ae", - "af", - "ak", - "am", - "an", - "ar", - "as", - "av", - "ay", - "az", - "ba", - "be", - "bg", - "bh", - "bi", - "bm", - "bn", - "bo", - "br", - "bs", - "ca", - "ce", - "ch", - "co", - "cr", - "cs", - "cu", - "cv", - "cy", - "da", - "de", - "dv", - "dz", - "ee", - "el", - "en", - "eo", - "es", - "et", - "eu", - "fa", - "ff", - "fi", - "fj", - "fo", - "fr", - "fy", - "ga", - "gd", - "gl", - "gn", - "gu", - "gv", - "ha", - "he", - "hi", - "ho", - "hr", - "ht", - "hu", - "hy", - "hz", - "ia", - "id", - "ie", - "ig", - "ii", - "ik", - "io", - "is", - "it", - "iu", - "ja", - "jv", - "ka", - "kg", - "ki", - "kj", - "kk", - "kl", - "km", - "kn", - "ko", - "kr", - "ks", - "ku", - "kv", - "kw", - "ky", - "la", - "lb", - "lg", - "li", - "ln", - "lo", - "lt", - "lu", - "lv", - "mg", - "mh", - "mi", - "mk", - "ml", - "mn", - "mr", - "ms", - "mt", - "my", - "na", - "nb", - "nd", - "ne", - "ng", - "nl", - "nn", - "no", - "nr", - "nv", - "ny", - "oc", - "oj", - "om", - "or", - "os", - "pa", - "pi", - "pl", - "ps", - "pt", - "qu", - "rm", - "rn", - "ro", - "ru", - "rw", - "sa", - "sc", - "sd", - "se", - "sg", - "si", - "sk", - "sl", - "sm", - "sn", - "so", - "sq", - "sr", - "ss", - "st", - "su", - "sv", - "sw", - "ta", - "te", - "tg", - "th", - "ti", - "tk", - "tl", - "tn", - "to", - "tr", - "ts", - "tt", - "tw", - "ty", - "ug", - "uk", - "ur", - "uz", - "ve", - "vi", - "vo", - "wa", - "wo", - "xh", - "yi", - "yo", - "za", - "zh", - "zu" - ], - - // From http://data.okfn.org/data/core/language-codes#resource-language-codes-full (IETF language tags) - locale_regions: [ - "agq-CM", - "asa-TZ", - "ast-ES", - "bas-CM", - "bem-ZM", - "bez-TZ", - "brx-IN", - "cgg-UG", - "chr-US", - "dav-KE", - "dje-NE", - "dsb-DE", - "dua-CM", - "dyo-SN", - "ebu-KE", - "ewo-CM", - "fil-PH", - "fur-IT", - "gsw-CH", - "gsw-FR", - "gsw-LI", - "guz-KE", - "haw-US", - "hsb-DE", - "jgo-CM", - "jmc-TZ", - "kab-DZ", - "kam-KE", - "kde-TZ", - "kea-CV", - "khq-ML", - "kkj-CM", - "kln-KE", - "kok-IN", - "ksb-TZ", - "ksf-CM", - "ksh-DE", - "lag-TZ", - "lkt-US", - "luo-KE", - "luy-KE", - "mas-KE", - "mas-TZ", - "mer-KE", - "mfe-MU", - "mgh-MZ", - "mgo-CM", - "mua-CM", - "naq-NA", - "nmg-CM", - "nnh-CM", - "nus-SD", - "nyn-UG", - "rof-TZ", - "rwk-TZ", - "sah-RU", - "saq-KE", - "sbp-TZ", - "seh-MZ", - "ses-ML", - "shi-Latn", - "shi-Latn-MA", - "shi-Tfng", - "shi-Tfng-MA", - "smn-FI", - "teo-KE", - "teo-UG", - "twq-NE", - "tzm-Latn", - "tzm-Latn-MA", - "vai-Latn", - "vai-Latn-LR", - "vai-Vaii", - "vai-Vaii-LR", - "vun-TZ", - "wae-CH", - "xog-UG", - "yav-CM", - "zgh-MA", - "af-NA", - "af-ZA", - "ak-GH", - "am-ET", - "ar-001", - "ar-AE", - "ar-BH", - "ar-DJ", - "ar-DZ", - "ar-EG", - "ar-EH", - "ar-ER", - "ar-IL", - "ar-IQ", - "ar-JO", - "ar-KM", - "ar-KW", - "ar-LB", - "ar-LY", - "ar-MA", - "ar-MR", - "ar-OM", - "ar-PS", - "ar-QA", - "ar-SA", - "ar-SD", - "ar-SO", - "ar-SS", - "ar-SY", - "ar-TD", - "ar-TN", - "ar-YE", - "as-IN", - "az-Cyrl", - "az-Cyrl-AZ", - "az-Latn", - "az-Latn-AZ", - "be-BY", - "bg-BG", - "bm-Latn", - "bm-Latn-ML", - "bn-BD", - "bn-IN", - "bo-CN", - "bo-IN", - "br-FR", - "bs-Cyrl", - "bs-Cyrl-BA", - "bs-Latn", - "bs-Latn-BA", - "ca-AD", - "ca-ES", - "ca-ES-VALENCIA", - "ca-FR", - "ca-IT", - "cs-CZ", - "cy-GB", - "da-DK", - "da-GL", - "de-AT", - "de-BE", - "de-CH", - "de-DE", - "de-LI", - "de-LU", - "dz-BT", - "ee-GH", - "ee-TG", - "el-CY", - "el-GR", - "en-001", - "en-150", - "en-AG", - "en-AI", - "en-AS", - "en-AU", - "en-BB", - "en-BE", - "en-BM", - "en-BS", - "en-BW", - "en-BZ", - "en-CA", - "en-CC", - "en-CK", - "en-CM", - "en-CX", - "en-DG", - "en-DM", - "en-ER", - "en-FJ", - "en-FK", - "en-FM", - "en-GB", - "en-GD", - "en-GG", - "en-GH", - "en-GI", - "en-GM", - "en-GU", - "en-GY", - "en-HK", - "en-IE", - "en-IM", - "en-IN", - "en-IO", - "en-JE", - "en-JM", - "en-KE", - "en-KI", - "en-KN", - "en-KY", - "en-LC", - "en-LR", - "en-LS", - "en-MG", - "en-MH", - "en-MO", - "en-MP", - "en-MS", - "en-MT", - "en-MU", - "en-MW", - "en-MY", - "en-NA", - "en-NF", - "en-NG", - "en-NR", - "en-NU", - "en-NZ", - "en-PG", - "en-PH", - "en-PK", - "en-PN", - "en-PR", - "en-PW", - "en-RW", - "en-SB", - "en-SC", - "en-SD", - "en-SG", - "en-SH", - "en-SL", - "en-SS", - "en-SX", - "en-SZ", - "en-TC", - "en-TK", - "en-TO", - "en-TT", - "en-TV", - "en-TZ", - "en-UG", - "en-UM", - "en-US", - "en-US-POSIX", - "en-VC", - "en-VG", - "en-VI", - "en-VU", - "en-WS", - "en-ZA", - "en-ZM", - "en-ZW", - "eo-001", - "es-419", - "es-AR", - "es-BO", - "es-CL", - "es-CO", - "es-CR", - "es-CU", - "es-DO", - "es-EA", - "es-EC", - "es-ES", - "es-GQ", - "es-GT", - "es-HN", - "es-IC", - "es-MX", - "es-NI", - "es-PA", - "es-PE", - "es-PH", - "es-PR", - "es-PY", - "es-SV", - "es-US", - "es-UY", - "es-VE", - "et-EE", - "eu-ES", - "fa-AF", - "fa-IR", - "ff-CM", - "ff-GN", - "ff-MR", - "ff-SN", - "fi-FI", - "fo-FO", - "fr-BE", - "fr-BF", - "fr-BI", - "fr-BJ", - "fr-BL", - "fr-CA", - "fr-CD", - "fr-CF", - "fr-CG", - "fr-CH", - "fr-CI", - "fr-CM", - "fr-DJ", - "fr-DZ", - "fr-FR", - "fr-GA", - "fr-GF", - "fr-GN", - "fr-GP", - "fr-GQ", - "fr-HT", - "fr-KM", - "fr-LU", - "fr-MA", - "fr-MC", - "fr-MF", - "fr-MG", - "fr-ML", - "fr-MQ", - "fr-MR", - "fr-MU", - "fr-NC", - "fr-NE", - "fr-PF", - "fr-PM", - "fr-RE", - "fr-RW", - "fr-SC", - "fr-SN", - "fr-SY", - "fr-TD", - "fr-TG", - "fr-TN", - "fr-VU", - "fr-WF", - "fr-YT", - "fy-NL", - "ga-IE", - "gd-GB", - "gl-ES", - "gu-IN", - "gv-IM", - "ha-Latn", - "ha-Latn-GH", - "ha-Latn-NE", - "ha-Latn-NG", - "he-IL", - "hi-IN", - "hr-BA", - "hr-HR", - "hu-HU", - "hy-AM", - "id-ID", - "ig-NG", - "ii-CN", - "is-IS", - "it-CH", - "it-IT", - "it-SM", - "ja-JP", - "ka-GE", - "ki-KE", - "kk-Cyrl", - "kk-Cyrl-KZ", - "kl-GL", - "km-KH", - "kn-IN", - "ko-KP", - "ko-KR", - "ks-Arab", - "ks-Arab-IN", - "kw-GB", - "ky-Cyrl", - "ky-Cyrl-KG", - "lb-LU", - "lg-UG", - "ln-AO", - "ln-CD", - "ln-CF", - "ln-CG", - "lo-LA", - "lt-LT", - "lu-CD", - "lv-LV", - "mg-MG", - "mk-MK", - "ml-IN", - "mn-Cyrl", - "mn-Cyrl-MN", - "mr-IN", - "ms-Latn", - "ms-Latn-BN", - "ms-Latn-MY", - "ms-Latn-SG", - "mt-MT", - "my-MM", - "nb-NO", - "nb-SJ", - "nd-ZW", - "ne-IN", - "ne-NP", - "nl-AW", - "nl-BE", - "nl-BQ", - "nl-CW", - "nl-NL", - "nl-SR", - "nl-SX", - "nn-NO", - "om-ET", - "om-KE", - "or-IN", - "os-GE", - "os-RU", - "pa-Arab", - "pa-Arab-PK", - "pa-Guru", - "pa-Guru-IN", - "pl-PL", - "ps-AF", - "pt-AO", - "pt-BR", - "pt-CV", - "pt-GW", - "pt-MO", - "pt-MZ", - "pt-PT", - "pt-ST", - "pt-TL", - "qu-BO", - "qu-EC", - "qu-PE", - "rm-CH", - "rn-BI", - "ro-MD", - "ro-RO", - "ru-BY", - "ru-KG", - "ru-KZ", - "ru-MD", - "ru-RU", - "ru-UA", - "rw-RW", - "se-FI", - "se-NO", - "se-SE", - "sg-CF", - "si-LK", - "sk-SK", - "sl-SI", - "sn-ZW", - "so-DJ", - "so-ET", - "so-KE", - "so-SO", - "sq-AL", - "sq-MK", - "sq-XK", - "sr-Cyrl", - "sr-Cyrl-BA", - "sr-Cyrl-ME", - "sr-Cyrl-RS", - "sr-Cyrl-XK", - "sr-Latn", - "sr-Latn-BA", - "sr-Latn-ME", - "sr-Latn-RS", - "sr-Latn-XK", - "sv-AX", - "sv-FI", - "sv-SE", - "sw-CD", - "sw-KE", - "sw-TZ", - "sw-UG", - "ta-IN", - "ta-LK", - "ta-MY", - "ta-SG", - "te-IN", - "th-TH", - "ti-ER", - "ti-ET", - "to-TO", - "tr-CY", - "tr-TR", - "ug-Arab", - "ug-Arab-CN", - "uk-UA", - "ur-IN", - "ur-PK", - "uz-Arab", - "uz-Arab-AF", - "uz-Cyrl", - "uz-Cyrl-UZ", - "uz-Latn", - "uz-Latn-UZ", - "vi-VN", - "yi-001", - "yo-BJ", - "yo-NG", - "zh-Hans", - "zh-Hans-CN", - "zh-Hans-HK", - "zh-Hans-MO", - "zh-Hans-SG", - "zh-Hant", - "zh-Hant-HK", - "zh-Hant-MO", - "zh-Hant-TW", - "zu-ZA" - ], - - us_states_and_dc: [ - {name: 'Alabama', abbreviation: 'AL'}, - {name: 'Alaska', abbreviation: 'AK'}, - {name: 'Arizona', abbreviation: 'AZ'}, - {name: 'Arkansas', abbreviation: 'AR'}, - {name: 'California', abbreviation: 'CA'}, - {name: 'Colorado', abbreviation: 'CO'}, - {name: 'Connecticut', abbreviation: 'CT'}, - {name: 'Delaware', abbreviation: 'DE'}, - {name: 'District of Columbia', abbreviation: 'DC'}, - {name: 'Florida', abbreviation: 'FL'}, - {name: 'Georgia', abbreviation: 'GA'}, - {name: 'Hawaii', abbreviation: 'HI'}, - {name: 'Idaho', abbreviation: 'ID'}, - {name: 'Illinois', abbreviation: 'IL'}, - {name: 'Indiana', abbreviation: 'IN'}, - {name: 'Iowa', abbreviation: 'IA'}, - {name: 'Kansas', abbreviation: 'KS'}, - {name: 'Kentucky', abbreviation: 'KY'}, - {name: 'Louisiana', abbreviation: 'LA'}, - {name: 'Maine', abbreviation: 'ME'}, - {name: 'Maryland', abbreviation: 'MD'}, - {name: 'Massachusetts', abbreviation: 'MA'}, - {name: 'Michigan', abbreviation: 'MI'}, - {name: 'Minnesota', abbreviation: 'MN'}, - {name: 'Mississippi', abbreviation: 'MS'}, - {name: 'Missouri', abbreviation: 'MO'}, - {name: 'Montana', abbreviation: 'MT'}, - {name: 'Nebraska', abbreviation: 'NE'}, - {name: 'Nevada', abbreviation: 'NV'}, - {name: 'New Hampshire', abbreviation: 'NH'}, - {name: 'New Jersey', abbreviation: 'NJ'}, - {name: 'New Mexico', abbreviation: 'NM'}, - {name: 'New York', abbreviation: 'NY'}, - {name: 'North Carolina', abbreviation: 'NC'}, - {name: 'North Dakota', abbreviation: 'ND'}, - {name: 'Ohio', abbreviation: 'OH'}, - {name: 'Oklahoma', abbreviation: 'OK'}, - {name: 'Oregon', abbreviation: 'OR'}, - {name: 'Pennsylvania', abbreviation: 'PA'}, - {name: 'Rhode Island', abbreviation: 'RI'}, - {name: 'South Carolina', abbreviation: 'SC'}, - {name: 'South Dakota', abbreviation: 'SD'}, - {name: 'Tennessee', abbreviation: 'TN'}, - {name: 'Texas', abbreviation: 'TX'}, - {name: 'Utah', abbreviation: 'UT'}, - {name: 'Vermont', abbreviation: 'VT'}, - {name: 'Virginia', abbreviation: 'VA'}, - {name: 'Washington', abbreviation: 'WA'}, - {name: 'West Virginia', abbreviation: 'WV'}, - {name: 'Wisconsin', abbreviation: 'WI'}, - {name: 'Wyoming', abbreviation: 'WY'} - ], - - territories: [ - {name: 'American Samoa', abbreviation: 'AS'}, - {name: 'Federated States of Micronesia', abbreviation: 'FM'}, - {name: 'Guam', abbreviation: 'GU'}, - {name: 'Marshall Islands', abbreviation: 'MH'}, - {name: 'Northern Mariana Islands', abbreviation: 'MP'}, - {name: 'Puerto Rico', abbreviation: 'PR'}, - {name: 'Virgin Islands, U.S.', abbreviation: 'VI'} - ], - - armed_forces: [ - {name: 'Armed Forces Europe', abbreviation: 'AE'}, - {name: 'Armed Forces Pacific', abbreviation: 'AP'}, - {name: 'Armed Forces the Americas', abbreviation: 'AA'} - ], - - country_regions: { - it: [ - { name: "Valle d'Aosta", abbreviation: "VDA" }, - { name: "Piemonte", abbreviation: "PIE" }, - { name: "Lombardia", abbreviation: "LOM" }, - { name: "Veneto", abbreviation: "VEN" }, - { name: "Trentino Alto Adige", abbreviation: "TAA" }, - { name: "Friuli Venezia Giulia", abbreviation: "FVG" }, - { name: "Liguria", abbreviation: "LIG" }, - { name: "Emilia Romagna", abbreviation: "EMR" }, - { name: "Toscana", abbreviation: "TOS" }, - { name: "Umbria", abbreviation: "UMB" }, - { name: "Marche", abbreviation: "MAR" }, - { name: "Abruzzo", abbreviation: "ABR" }, - { name: "Lazio", abbreviation: "LAZ" }, - { name: "Campania", abbreviation: "CAM" }, - { name: "Puglia", abbreviation: "PUG" }, - { name: "Basilicata", abbreviation: "BAS" }, - { name: "Molise", abbreviation: "MOL" }, - { name: "Calabria", abbreviation: "CAL" }, - { name: "Sicilia", abbreviation: "SIC" }, - { name: "Sardegna", abbreviation: "SAR" } - ], - mx: [ - { name: 'Aguascalientes', abbreviation: 'AGU' }, - { name: 'Baja California', abbreviation: 'BCN' }, - { name: 'Baja California Sur', abbreviation: 'BCS' }, - { name: 'Campeche', abbreviation: 'CAM' }, - { name: 'Chiapas', abbreviation: 'CHP' }, - { name: 'Chihuahua', abbreviation: 'CHH' }, - { name: 'Ciudad de México', abbreviation: 'DIF' }, - { name: 'Coahuila', abbreviation: 'COA' }, - { name: 'Colima', abbreviation: 'COL' }, - { name: 'Durango', abbreviation: 'DUR' }, - { name: 'Guanajuato', abbreviation: 'GUA' }, - { name: 'Guerrero', abbreviation: 'GRO' }, - { name: 'Hidalgo', abbreviation: 'HID' }, - { name: 'Jalisco', abbreviation: 'JAL' }, - { name: 'México', abbreviation: 'MEX' }, - { name: 'Michoacán', abbreviation: 'MIC' }, - { name: 'Morelos', abbreviation: 'MOR' }, - { name: 'Nayarit', abbreviation: 'NAY' }, - { name: 'Nuevo León', abbreviation: 'NLE' }, - { name: 'Oaxaca', abbreviation: 'OAX' }, - { name: 'Puebla', abbreviation: 'PUE' }, - { name: 'Querétaro', abbreviation: 'QUE' }, - { name: 'Quintana Roo', abbreviation: 'ROO' }, - { name: 'San Luis Potosí', abbreviation: 'SLP' }, - { name: 'Sinaloa', abbreviation: 'SIN' }, - { name: 'Sonora', abbreviation: 'SON' }, - { name: 'Tabasco', abbreviation: 'TAB' }, - { name: 'Tamaulipas', abbreviation: 'TAM' }, - { name: 'Tlaxcala', abbreviation: 'TLA' }, - { name: 'Veracruz', abbreviation: 'VER' }, - { name: 'Yucatán', abbreviation: 'YUC' }, - { name: 'Zacatecas', abbreviation: 'ZAC' } - ] - }, - - street_suffixes: { - 'us': [ - {name: 'Avenue', abbreviation: 'Ave'}, - {name: 'Boulevard', abbreviation: 'Blvd'}, - {name: 'Center', abbreviation: 'Ctr'}, - {name: 'Circle', abbreviation: 'Cir'}, - {name: 'Court', abbreviation: 'Ct'}, - {name: 'Drive', abbreviation: 'Dr'}, - {name: 'Extension', abbreviation: 'Ext'}, - {name: 'Glen', abbreviation: 'Gln'}, - {name: 'Grove', abbreviation: 'Grv'}, - {name: 'Heights', abbreviation: 'Hts'}, - {name: 'Highway', abbreviation: 'Hwy'}, - {name: 'Junction', abbreviation: 'Jct'}, - {name: 'Key', abbreviation: 'Key'}, - {name: 'Lane', abbreviation: 'Ln'}, - {name: 'Loop', abbreviation: 'Loop'}, - {name: 'Manor', abbreviation: 'Mnr'}, - {name: 'Mill', abbreviation: 'Mill'}, - {name: 'Park', abbreviation: 'Park'}, - {name: 'Parkway', abbreviation: 'Pkwy'}, - {name: 'Pass', abbreviation: 'Pass'}, - {name: 'Path', abbreviation: 'Path'}, - {name: 'Pike', abbreviation: 'Pike'}, - {name: 'Place', abbreviation: 'Pl'}, - {name: 'Plaza', abbreviation: 'Plz'}, - {name: 'Point', abbreviation: 'Pt'}, - {name: 'Ridge', abbreviation: 'Rdg'}, - {name: 'River', abbreviation: 'Riv'}, - {name: 'Road', abbreviation: 'Rd'}, - {name: 'Square', abbreviation: 'Sq'}, - {name: 'Street', abbreviation: 'St'}, - {name: 'Terrace', abbreviation: 'Ter'}, - {name: 'Trail', abbreviation: 'Trl'}, - {name: 'Turnpike', abbreviation: 'Tpke'}, - {name: 'View', abbreviation: 'Vw'}, - {name: 'Way', abbreviation: 'Way'} - ], - 'it': [ - { name: 'Accesso', abbreviation: 'Acc.' }, - { name: 'Alzaia', abbreviation: 'Alz.' }, - { name: 'Arco', abbreviation: 'Arco' }, - { name: 'Archivolto', abbreviation: 'Acv.' }, - { name: 'Arena', abbreviation: 'Arena' }, - { name: 'Argine', abbreviation: 'Argine' }, - { name: 'Bacino', abbreviation: 'Bacino' }, - { name: 'Banchi', abbreviation: 'Banchi' }, - { name: 'Banchina', abbreviation: 'Ban.' }, - { name: 'Bastioni', abbreviation: 'Bas.' }, - { name: 'Belvedere', abbreviation: 'Belv.' }, - { name: 'Borgata', abbreviation: 'B.ta' }, - { name: 'Borgo', abbreviation: 'B.go' }, - { name: 'Calata', abbreviation: 'Cal.' }, - { name: 'Calle', abbreviation: 'Calle' }, - { name: 'Campiello', abbreviation: 'Cam.' }, - { name: 'Campo', abbreviation: 'Cam.' }, - { name: 'Canale', abbreviation: 'Can.' }, - { name: 'Carraia', abbreviation: 'Carr.' }, - { name: 'Cascina', abbreviation: 'Cascina' }, - { name: 'Case sparse', abbreviation: 'c.s.' }, - { name: 'Cavalcavia', abbreviation: 'Cv.' }, - { name: 'Circonvallazione', abbreviation: 'Cv.' }, - { name: 'Complanare', abbreviation: 'C.re' }, - { name: 'Contrada', abbreviation: 'C.da' }, - { name: 'Corso', abbreviation: 'C.so' }, - { name: 'Corte', abbreviation: 'C.te' }, - { name: 'Cortile', abbreviation: 'C.le' }, - { name: 'Diramazione', abbreviation: 'Dir.' }, - { name: 'Fondaco', abbreviation: 'F.co' }, - { name: 'Fondamenta', abbreviation: 'F.ta' }, - { name: 'Fondo', abbreviation: 'F.do' }, - { name: 'Frazione', abbreviation: 'Fr.' }, - { name: 'Isola', abbreviation: 'Is.' }, - { name: 'Largo', abbreviation: 'L.go' }, - { name: 'Litoranea', abbreviation: 'Lit.' }, - { name: 'Lungolago', abbreviation: 'L.go lago' }, - { name: 'Lungo Po', abbreviation: 'l.go Po' }, - { name: 'Molo', abbreviation: 'Molo' }, - { name: 'Mura', abbreviation: 'Mura' }, - { name: 'Passaggio privato', abbreviation: 'pass. priv.' }, - { name: 'Passeggiata', abbreviation: 'Pass.' }, - { name: 'Piazza', abbreviation: 'P.zza' }, - { name: 'Piazzale', abbreviation: 'P.le' }, - { name: 'Ponte', abbreviation: 'P.te' }, - { name: 'Portico', abbreviation: 'P.co' }, - { name: 'Rampa', abbreviation: 'Rampa' }, - { name: 'Regione', abbreviation: 'Reg.' }, - { name: 'Rione', abbreviation: 'R.ne' }, - { name: 'Rio', abbreviation: 'Rio' }, - { name: 'Ripa', abbreviation: 'Ripa' }, - { name: 'Riva', abbreviation: 'Riva' }, - { name: 'Rondò', abbreviation: 'Rondò' }, - { name: 'Rotonda', abbreviation: 'Rot.' }, - { name: 'Sagrato', abbreviation: 'Sagr.' }, - { name: 'Salita', abbreviation: 'Sal.' }, - { name: 'Scalinata', abbreviation: 'Scal.' }, - { name: 'Scalone', abbreviation: 'Scal.' }, - { name: 'Slargo', abbreviation: 'Sl.' }, - { name: 'Sottoportico', abbreviation: 'Sott.' }, - { name: 'Strada', abbreviation: 'Str.' }, - { name: 'Stradale', abbreviation: 'Str.le' }, - { name: 'Strettoia', abbreviation: 'Strett.' }, - { name: 'Traversa', abbreviation: 'Trav.' }, - { name: 'Via', abbreviation: 'V.' }, - { name: 'Viale', abbreviation: 'V.le' }, - { name: 'Vicinale', abbreviation: 'Vic.le' }, - { name: 'Vicolo', abbreviation: 'Vic.' } - ], - 'uk' : [ - {name: 'Avenue', abbreviation: 'Ave'}, - {name: 'Close', abbreviation: 'Cl'}, - {name: 'Court', abbreviation: 'Ct'}, - {name: 'Crescent', abbreviation: 'Cr'}, - {name: 'Drive', abbreviation: 'Dr'}, - {name: 'Garden', abbreviation: 'Gdn'}, - {name: 'Gardens', abbreviation: 'Gdns'}, - {name: 'Green', abbreviation: 'Gn'}, - {name: 'Grove', abbreviation: 'Gr'}, - {name: 'Lane', abbreviation: 'Ln'}, - {name: 'Mount', abbreviation: 'Mt'}, - {name: 'Place', abbreviation: 'Pl'}, - {name: 'Park', abbreviation: 'Pk'}, - {name: 'Ridge', abbreviation: 'Rdg'}, - {name: 'Road', abbreviation: 'Rd'}, - {name: 'Square', abbreviation: 'Sq'}, - {name: 'Street', abbreviation: 'St'}, - {name: 'Terrace', abbreviation: 'Ter'}, - {name: 'Valley', abbreviation: 'Val'} - ] - }, - - months: [ - {name: 'January', short_name: 'Jan', numeric: '01', days: 31}, - // Not messing with leap years... - {name: 'February', short_name: 'Feb', numeric: '02', days: 28}, - {name: 'March', short_name: 'Mar', numeric: '03', days: 31}, - {name: 'April', short_name: 'Apr', numeric: '04', days: 30}, - {name: 'May', short_name: 'May', numeric: '05', days: 31}, - {name: 'June', short_name: 'Jun', numeric: '06', days: 30}, - {name: 'July', short_name: 'Jul', numeric: '07', days: 31}, - {name: 'August', short_name: 'Aug', numeric: '08', days: 31}, - {name: 'September', short_name: 'Sep', numeric: '09', days: 30}, - {name: 'October', short_name: 'Oct', numeric: '10', days: 31}, - {name: 'November', short_name: 'Nov', numeric: '11', days: 30}, - {name: 'December', short_name: 'Dec', numeric: '12', days: 31} - ], - - // http://en.wikipedia.org/wiki/Bank_card_number#Issuer_identification_number_.28IIN.29 - cc_types: [ - {name: "American Express", short_name: 'amex', prefix: '34', length: 15}, - {name: "Bankcard", short_name: 'bankcard', prefix: '5610', length: 16}, - {name: "China UnionPay", short_name: 'chinaunion', prefix: '62', length: 16}, - {name: "Diners Club Carte Blanche", short_name: 'dccarte', prefix: '300', length: 14}, - {name: "Diners Club enRoute", short_name: 'dcenroute', prefix: '2014', length: 15}, - {name: "Diners Club International", short_name: 'dcintl', prefix: '36', length: 14}, - {name: "Diners Club United States & Canada", short_name: 'dcusc', prefix: '54', length: 16}, - {name: "Discover Card", short_name: 'discover', prefix: '6011', length: 16}, - {name: "InstaPayment", short_name: 'instapay', prefix: '637', length: 16}, - {name: "JCB", short_name: 'jcb', prefix: '3528', length: 16}, - {name: "Laser", short_name: 'laser', prefix: '6304', length: 16}, - {name: "Maestro", short_name: 'maestro', prefix: '5018', length: 16}, - {name: "Mastercard", short_name: 'mc', prefix: '51', length: 16}, - {name: "Solo", short_name: 'solo', prefix: '6334', length: 16}, - {name: "Switch", short_name: 'switch', prefix: '4903', length: 16}, - {name: "Visa", short_name: 'visa', prefix: '4', length: 16}, - {name: "Visa Electron", short_name: 'electron', prefix: '4026', length: 16} - ], - - //return all world currency by ISO 4217 - currency_types: [ - {'code' : 'AED', 'name' : 'United Arab Emirates Dirham'}, - {'code' : 'AFN', 'name' : 'Afghanistan Afghani'}, - {'code' : 'ALL', 'name' : 'Albania Lek'}, - {'code' : 'AMD', 'name' : 'Armenia Dram'}, - {'code' : 'ANG', 'name' : 'Netherlands Antilles Guilder'}, - {'code' : 'AOA', 'name' : 'Angola Kwanza'}, - {'code' : 'ARS', 'name' : 'Argentina Peso'}, - {'code' : 'AUD', 'name' : 'Australia Dollar'}, - {'code' : 'AWG', 'name' : 'Aruba Guilder'}, - {'code' : 'AZN', 'name' : 'Azerbaijan New Manat'}, - {'code' : 'BAM', 'name' : 'Bosnia and Herzegovina Convertible Marka'}, - {'code' : 'BBD', 'name' : 'Barbados Dollar'}, - {'code' : 'BDT', 'name' : 'Bangladesh Taka'}, - {'code' : 'BGN', 'name' : 'Bulgaria Lev'}, - {'code' : 'BHD', 'name' : 'Bahrain Dinar'}, - {'code' : 'BIF', 'name' : 'Burundi Franc'}, - {'code' : 'BMD', 'name' : 'Bermuda Dollar'}, - {'code' : 'BND', 'name' : 'Brunei Darussalam Dollar'}, - {'code' : 'BOB', 'name' : 'Bolivia Boliviano'}, - {'code' : 'BRL', 'name' : 'Brazil Real'}, - {'code' : 'BSD', 'name' : 'Bahamas Dollar'}, - {'code' : 'BTN', 'name' : 'Bhutan Ngultrum'}, - {'code' : 'BWP', 'name' : 'Botswana Pula'}, - {'code' : 'BYR', 'name' : 'Belarus Ruble'}, - {'code' : 'BZD', 'name' : 'Belize Dollar'}, - {'code' : 'CAD', 'name' : 'Canada Dollar'}, - {'code' : 'CDF', 'name' : 'Congo/Kinshasa Franc'}, - {'code' : 'CHF', 'name' : 'Switzerland Franc'}, - {'code' : 'CLP', 'name' : 'Chile Peso'}, - {'code' : 'CNY', 'name' : 'China Yuan Renminbi'}, - {'code' : 'COP', 'name' : 'Colombia Peso'}, - {'code' : 'CRC', 'name' : 'Costa Rica Colon'}, - {'code' : 'CUC', 'name' : 'Cuba Convertible Peso'}, - {'code' : 'CUP', 'name' : 'Cuba Peso'}, - {'code' : 'CVE', 'name' : 'Cape Verde Escudo'}, - {'code' : 'CZK', 'name' : 'Czech Republic Koruna'}, - {'code' : 'DJF', 'name' : 'Djibouti Franc'}, - {'code' : 'DKK', 'name' : 'Denmark Krone'}, - {'code' : 'DOP', 'name' : 'Dominican Republic Peso'}, - {'code' : 'DZD', 'name' : 'Algeria Dinar'}, - {'code' : 'EGP', 'name' : 'Egypt Pound'}, - {'code' : 'ERN', 'name' : 'Eritrea Nakfa'}, - {'code' : 'ETB', 'name' : 'Ethiopia Birr'}, - {'code' : 'EUR', 'name' : 'Euro Member Countries'}, - {'code' : 'FJD', 'name' : 'Fiji Dollar'}, - {'code' : 'FKP', 'name' : 'Falkland Islands (Malvinas) Pound'}, - {'code' : 'GBP', 'name' : 'United Kingdom Pound'}, - {'code' : 'GEL', 'name' : 'Georgia Lari'}, - {'code' : 'GGP', 'name' : 'Guernsey Pound'}, - {'code' : 'GHS', 'name' : 'Ghana Cedi'}, - {'code' : 'GIP', 'name' : 'Gibraltar Pound'}, - {'code' : 'GMD', 'name' : 'Gambia Dalasi'}, - {'code' : 'GNF', 'name' : 'Guinea Franc'}, - {'code' : 'GTQ', 'name' : 'Guatemala Quetzal'}, - {'code' : 'GYD', 'name' : 'Guyana Dollar'}, - {'code' : 'HKD', 'name' : 'Hong Kong Dollar'}, - {'code' : 'HNL', 'name' : 'Honduras Lempira'}, - {'code' : 'HRK', 'name' : 'Croatia Kuna'}, - {'code' : 'HTG', 'name' : 'Haiti Gourde'}, - {'code' : 'HUF', 'name' : 'Hungary Forint'}, - {'code' : 'IDR', 'name' : 'Indonesia Rupiah'}, - {'code' : 'ILS', 'name' : 'Israel Shekel'}, - {'code' : 'IMP', 'name' : 'Isle of Man Pound'}, - {'code' : 'INR', 'name' : 'India Rupee'}, - {'code' : 'IQD', 'name' : 'Iraq Dinar'}, - {'code' : 'IRR', 'name' : 'Iran Rial'}, - {'code' : 'ISK', 'name' : 'Iceland Krona'}, - {'code' : 'JEP', 'name' : 'Jersey Pound'}, - {'code' : 'JMD', 'name' : 'Jamaica Dollar'}, - {'code' : 'JOD', 'name' : 'Jordan Dinar'}, - {'code' : 'JPY', 'name' : 'Japan Yen'}, - {'code' : 'KES', 'name' : 'Kenya Shilling'}, - {'code' : 'KGS', 'name' : 'Kyrgyzstan Som'}, - {'code' : 'KHR', 'name' : 'Cambodia Riel'}, - {'code' : 'KMF', 'name' : 'Comoros Franc'}, - {'code' : 'KPW', 'name' : 'Korea (North) Won'}, - {'code' : 'KRW', 'name' : 'Korea (South) Won'}, - {'code' : 'KWD', 'name' : 'Kuwait Dinar'}, - {'code' : 'KYD', 'name' : 'Cayman Islands Dollar'}, - {'code' : 'KZT', 'name' : 'Kazakhstan Tenge'}, - {'code' : 'LAK', 'name' : 'Laos Kip'}, - {'code' : 'LBP', 'name' : 'Lebanon Pound'}, - {'code' : 'LKR', 'name' : 'Sri Lanka Rupee'}, - {'code' : 'LRD', 'name' : 'Liberia Dollar'}, - {'code' : 'LSL', 'name' : 'Lesotho Loti'}, - {'code' : 'LTL', 'name' : 'Lithuania Litas'}, - {'code' : 'LYD', 'name' : 'Libya Dinar'}, - {'code' : 'MAD', 'name' : 'Morocco Dirham'}, - {'code' : 'MDL', 'name' : 'Moldova Leu'}, - {'code' : 'MGA', 'name' : 'Madagascar Ariary'}, - {'code' : 'MKD', 'name' : 'Macedonia Denar'}, - {'code' : 'MMK', 'name' : 'Myanmar (Burma) Kyat'}, - {'code' : 'MNT', 'name' : 'Mongolia Tughrik'}, - {'code' : 'MOP', 'name' : 'Macau Pataca'}, - {'code' : 'MRO', 'name' : 'Mauritania Ouguiya'}, - {'code' : 'MUR', 'name' : 'Mauritius Rupee'}, - {'code' : 'MVR', 'name' : 'Maldives (Maldive Islands) Rufiyaa'}, - {'code' : 'MWK', 'name' : 'Malawi Kwacha'}, - {'code' : 'MXN', 'name' : 'Mexico Peso'}, - {'code' : 'MYR', 'name' : 'Malaysia Ringgit'}, - {'code' : 'MZN', 'name' : 'Mozambique Metical'}, - {'code' : 'NAD', 'name' : 'Namibia Dollar'}, - {'code' : 'NGN', 'name' : 'Nigeria Naira'}, - {'code' : 'NIO', 'name' : 'Nicaragua Cordoba'}, - {'code' : 'NOK', 'name' : 'Norway Krone'}, - {'code' : 'NPR', 'name' : 'Nepal Rupee'}, - {'code' : 'NZD', 'name' : 'New Zealand Dollar'}, - {'code' : 'OMR', 'name' : 'Oman Rial'}, - {'code' : 'PAB', 'name' : 'Panama Balboa'}, - {'code' : 'PEN', 'name' : 'Peru Nuevo Sol'}, - {'code' : 'PGK', 'name' : 'Papua New Guinea Kina'}, - {'code' : 'PHP', 'name' : 'Philippines Peso'}, - {'code' : 'PKR', 'name' : 'Pakistan Rupee'}, - {'code' : 'PLN', 'name' : 'Poland Zloty'}, - {'code' : 'PYG', 'name' : 'Paraguay Guarani'}, - {'code' : 'QAR', 'name' : 'Qatar Riyal'}, - {'code' : 'RON', 'name' : 'Romania New Leu'}, - {'code' : 'RSD', 'name' : 'Serbia Dinar'}, - {'code' : 'RUB', 'name' : 'Russia Ruble'}, - {'code' : 'RWF', 'name' : 'Rwanda Franc'}, - {'code' : 'SAR', 'name' : 'Saudi Arabia Riyal'}, - {'code' : 'SBD', 'name' : 'Solomon Islands Dollar'}, - {'code' : 'SCR', 'name' : 'Seychelles Rupee'}, - {'code' : 'SDG', 'name' : 'Sudan Pound'}, - {'code' : 'SEK', 'name' : 'Sweden Krona'}, - {'code' : 'SGD', 'name' : 'Singapore Dollar'}, - {'code' : 'SHP', 'name' : 'Saint Helena Pound'}, - {'code' : 'SLL', 'name' : 'Sierra Leone Leone'}, - {'code' : 'SOS', 'name' : 'Somalia Shilling'}, - {'code' : 'SPL', 'name' : 'Seborga Luigino'}, - {'code' : 'SRD', 'name' : 'Suriname Dollar'}, - {'code' : 'STD', 'name' : 'São Tomé and Príncipe Dobra'}, - {'code' : 'SVC', 'name' : 'El Salvador Colon'}, - {'code' : 'SYP', 'name' : 'Syria Pound'}, - {'code' : 'SZL', 'name' : 'Swaziland Lilangeni'}, - {'code' : 'THB', 'name' : 'Thailand Baht'}, - {'code' : 'TJS', 'name' : 'Tajikistan Somoni'}, - {'code' : 'TMT', 'name' : 'Turkmenistan Manat'}, - {'code' : 'TND', 'name' : 'Tunisia Dinar'}, - {'code' : 'TOP', 'name' : 'Tonga Pa\'anga'}, - {'code' : 'TRY', 'name' : 'Turkey Lira'}, - {'code' : 'TTD', 'name' : 'Trinidad and Tobago Dollar'}, - {'code' : 'TVD', 'name' : 'Tuvalu Dollar'}, - {'code' : 'TWD', 'name' : 'Taiwan New Dollar'}, - {'code' : 'TZS', 'name' : 'Tanzania Shilling'}, - {'code' : 'UAH', 'name' : 'Ukraine Hryvnia'}, - {'code' : 'UGX', 'name' : 'Uganda Shilling'}, - {'code' : 'USD', 'name' : 'United States Dollar'}, - {'code' : 'UYU', 'name' : 'Uruguay Peso'}, - {'code' : 'UZS', 'name' : 'Uzbekistan Som'}, - {'code' : 'VEF', 'name' : 'Venezuela Bolivar'}, - {'code' : 'VND', 'name' : 'Viet Nam Dong'}, - {'code' : 'VUV', 'name' : 'Vanuatu Vatu'}, - {'code' : 'WST', 'name' : 'Samoa Tala'}, - {'code' : 'XAF', 'name' : 'Communauté Financière Africaine (BEAC) CFA Franc BEAC'}, - {'code' : 'XCD', 'name' : 'East Caribbean Dollar'}, - {'code' : 'XDR', 'name' : 'International Monetary Fund (IMF) Special Drawing Rights'}, - {'code' : 'XOF', 'name' : 'Communauté Financière Africaine (BCEAO) Franc'}, - {'code' : 'XPF', 'name' : 'Comptoirs Français du Pacifique (CFP) Franc'}, - {'code' : 'YER', 'name' : 'Yemen Rial'}, - {'code' : 'ZAR', 'name' : 'South Africa Rand'}, - {'code' : 'ZMW', 'name' : 'Zambia Kwacha'}, - {'code' : 'ZWD', 'name' : 'Zimbabwe Dollar'} - ], - - // return the names of all valide colors - colorNames : [ "AliceBlue", "Black", "Navy", "DarkBlue", "MediumBlue", "Blue", "DarkGreen", "Green", "Teal", "DarkCyan", "DeepSkyBlue", "DarkTurquoise", "MediumSpringGreen", "Lime", "SpringGreen", - "Aqua", "Cyan", "MidnightBlue", "DodgerBlue", "LightSeaGreen", "ForestGreen", "SeaGreen", "DarkSlateGray", "LimeGreen", "MediumSeaGreen", "Turquoise", "RoyalBlue", "SteelBlue", "DarkSlateBlue", "MediumTurquoise", - "Indigo", "DarkOliveGreen", "CadetBlue", "CornflowerBlue", "RebeccaPurple", "MediumAquaMarine", "DimGray", "SlateBlue", "OliveDrab", "SlateGray", "LightSlateGray", "MediumSlateBlue", "LawnGreen", "Chartreuse", - "Aquamarine", "Maroon", "Purple", "Olive", "Gray", "SkyBlue", "LightSkyBlue", "BlueViolet", "DarkRed", "DarkMagenta", "SaddleBrown", "Ivory", "White", - "DarkSeaGreen", "LightGreen", "MediumPurple", "DarkViolet", "PaleGreen", "DarkOrchid", "YellowGreen", "Sienna", "Brown", "DarkGray", "LightBlue", "GreenYellow", "PaleTurquoise", "LightSteelBlue", "PowderBlue", - "FireBrick", "DarkGoldenRod", "MediumOrchid", "RosyBrown", "DarkKhaki", "Silver", "MediumVioletRed", "IndianRed", "Peru", "Chocolate", "Tan", "LightGray", "Thistle", "Orchid", "GoldenRod", "PaleVioletRed", - "Crimson", "Gainsboro", "Plum", "BurlyWood", "LightCyan", "Lavender", "DarkSalmon", "Violet", "PaleGoldenRod", "LightCoral", "Khaki", "AliceBlue", "HoneyDew", "Azure", "SandyBrown", "Wheat", "Beige", "WhiteSmoke", - "MintCream", "GhostWhite", "Salmon", "AntiqueWhite", "Linen", "LightGoldenRodYellow", "OldLace", "Red", "Fuchsia", "Magenta", "DeepPink", "OrangeRed", "Tomato", "HotPink", "Coral", "DarkOrange", "LightSalmon", "Orange", - "LightPink", "Pink", "Gold", "PeachPuff", "NavajoWhite", "Moccasin", "Bisque", "MistyRose", "BlanchedAlmond", "PapayaWhip", "LavenderBlush", "SeaShell", "Cornsilk", "LemonChiffon", "FloralWhite", "Snow", "Yellow", "LightYellow" - ], - - // Data taken from https://www.sec.gov/rules/other/4-460list.htm - company: [ "3Com Corp", - "3M Company", - "A.G. Edwards Inc.", - "Abbott Laboratories", - "Abercrombie & Fitch Co.", - "ABM Industries Incorporated", - "Ace Hardware Corporation", - "ACT Manufacturing Inc.", - "Acterna Corp.", - "Adams Resources & Energy, Inc.", - "ADC Telecommunications, Inc.", - "Adelphia Communications Corporation", - "Administaff, Inc.", - "Adobe Systems Incorporated", - "Adolph Coors Company", - "Advance Auto Parts, Inc.", - "Advanced Micro Devices, Inc.", - "AdvancePCS, Inc.", - "Advantica Restaurant Group, Inc.", - "The AES Corporation", - "Aetna Inc.", - "Affiliated Computer Services, Inc.", - "AFLAC Incorporated", - "AGCO Corporation", - "Agilent Technologies, Inc.", - "Agway Inc.", - "Apartment Investment and Management Company", - "Air Products and Chemicals, Inc.", - "Airborne, Inc.", - "Airgas, Inc.", - "AK Steel Holding Corporation", - "Alaska Air Group, Inc.", - "Alberto-Culver Company", - "Albertson's, Inc.", - "Alcoa Inc.", - "Alleghany Corporation", - "Allegheny Energy, Inc.", - "Allegheny Technologies Incorporated", - "Allergan, Inc.", - "ALLETE, Inc.", - "Alliant Energy Corporation", - "Allied Waste Industries, Inc.", - "Allmerica Financial Corporation", - "The Allstate Corporation", - "ALLTEL Corporation", - "The Alpine Group, Inc.", - "Amazon.com, Inc.", - "AMC Entertainment Inc.", - "American Power Conversion Corporation", - "Amerada Hess Corporation", - "AMERCO", - "Ameren Corporation", - "America West Holdings Corporation", - "American Axle & Manufacturing Holdings, Inc.", - "American Eagle Outfitters, Inc.", - "American Electric Power Company, Inc.", - "American Express Company", - "American Financial Group, Inc.", - "American Greetings Corporation", - "American International Group, Inc.", - "American Standard Companies Inc.", - "American Water Works Company, Inc.", - "AmerisourceBergen Corporation", - "Ames Department Stores, Inc.", - "Amgen Inc.", - "Amkor Technology, Inc.", - "AMR Corporation", - "AmSouth Bancorp.", - "Amtran, Inc.", - "Anadarko Petroleum Corporation", - "Analog Devices, Inc.", - "Anheuser-Busch Companies, Inc.", - "Anixter International Inc.", - "AnnTaylor Inc.", - "Anthem, Inc.", - "AOL Time Warner Inc.", - "Aon Corporation", - "Apache Corporation", - "Apple Computer, Inc.", - "Applera Corporation", - "Applied Industrial Technologies, Inc.", - "Applied Materials, Inc.", - "Aquila, Inc.", - "ARAMARK Corporation", - "Arch Coal, Inc.", - "Archer Daniels Midland Company", - "Arkansas Best Corporation", - "Armstrong Holdings, Inc.", - "Arrow Electronics, Inc.", - "ArvinMeritor, Inc.", - "Ashland Inc.", - "Astoria Financial Corporation", - "AT&T Corp.", - "Atmel Corporation", - "Atmos Energy Corporation", - "Audiovox Corporation", - "Autoliv, Inc.", - "Automatic Data Processing, Inc.", - "AutoNation, Inc.", - "AutoZone, Inc.", - "Avaya Inc.", - "Avery Dennison Corporation", - "Avista Corporation", - "Avnet, Inc.", - "Avon Products, Inc.", - "Baker Hughes Incorporated", - "Ball Corporation", - "Bank of America Corporation", - "The Bank of New York Company, Inc.", - "Bank One Corporation", - "Banknorth Group, Inc.", - "Banta Corporation", - "Barnes & Noble, Inc.", - "Bausch & Lomb Incorporated", - "Baxter International Inc.", - "BB&T Corporation", - "The Bear Stearns Companies Inc.", - "Beazer Homes USA, Inc.", - "Beckman Coulter, Inc.", - "Becton, Dickinson and Company", - "Bed Bath & Beyond Inc.", - "Belk, Inc.", - "Bell Microproducts Inc.", - "BellSouth Corporation", - "Belo Corp.", - "Bemis Company, Inc.", - "Benchmark Electronics, Inc.", - "Berkshire Hathaway Inc.", - "Best Buy Co., Inc.", - "Bethlehem Steel Corporation", - "Beverly Enterprises, Inc.", - "Big Lots, Inc.", - "BJ Services Company", - "BJ's Wholesale Club, Inc.", - "The Black & Decker Corporation", - "Black Hills Corporation", - "BMC Software, Inc.", - "The Boeing Company", - "Boise Cascade Corporation", - "Borders Group, Inc.", - "BorgWarner Inc.", - "Boston Scientific Corporation", - "Bowater Incorporated", - "Briggs & Stratton Corporation", - "Brightpoint, Inc.", - "Brinker International, Inc.", - "Bristol-Myers Squibb Company", - "Broadwing, Inc.", - "Brown Shoe Company, Inc.", - "Brown-Forman Corporation", - "Brunswick Corporation", - "Budget Group, Inc.", - "Burlington Coat Factory Warehouse Corporation", - "Burlington Industries, Inc.", - "Burlington Northern Santa Fe Corporation", - "Burlington Resources Inc.", - "C. H. Robinson Worldwide Inc.", - "Cablevision Systems Corp", - "Cabot Corp", - "Cadence Design Systems, Inc.", - "Calpine Corp.", - "Campbell Soup Co.", - "Capital One Financial Corp.", - "Cardinal Health Inc.", - "Caremark Rx Inc.", - "Carlisle Cos. Inc.", - "Carpenter Technology Corp.", - "Casey's General Stores Inc.", - "Caterpillar Inc.", - "CBRL Group Inc.", - "CDI Corp.", - "CDW Computer Centers Inc.", - "CellStar Corp.", - "Cendant Corp", - "Cenex Harvest States Cooperatives", - "Centex Corp.", - "CenturyTel Inc.", - "Ceridian Corp.", - "CH2M Hill Cos. Ltd.", - "Champion Enterprises Inc.", - "Charles Schwab Corp.", - "Charming Shoppes Inc.", - "Charter Communications Inc.", - "Charter One Financial Inc.", - "ChevronTexaco Corp.", - "Chiquita Brands International Inc.", - "Chubb Corp", - "Ciena Corp.", - "Cigna Corp", - "Cincinnati Financial Corp.", - "Cinergy Corp.", - "Cintas Corp.", - "Circuit City Stores Inc.", - "Cisco Systems Inc.", - "Citigroup, Inc", - "Citizens Communications Co.", - "CKE Restaurants Inc.", - "Clear Channel Communications Inc.", - "The Clorox Co.", - "CMGI Inc.", - "CMS Energy Corp.", - "CNF Inc.", - "Coca-Cola Co.", - "Coca-Cola Enterprises Inc.", - "Colgate-Palmolive Co.", - "Collins & Aikman Corp.", - "Comcast Corp.", - "Comdisco Inc.", - "Comerica Inc.", - "Comfort Systems USA Inc.", - "Commercial Metals Co.", - "Community Health Systems Inc.", - "Compass Bancshares Inc", - "Computer Associates International Inc.", - "Computer Sciences Corp.", - "Compuware Corp.", - "Comverse Technology Inc.", - "ConAgra Foods Inc.", - "Concord EFS Inc.", - "Conectiv, Inc", - "Conoco Inc", - "Conseco Inc.", - "Consolidated Freightways Corp.", - "Consolidated Edison Inc.", - "Constellation Brands Inc.", - "Constellation Emergy Group Inc.", - "Continental Airlines Inc.", - "Convergys Corp.", - "Cooper Cameron Corp.", - "Cooper Industries Ltd.", - "Cooper Tire & Rubber Co.", - "Corn Products International Inc.", - "Corning Inc.", - "Costco Wholesale Corp.", - "Countrywide Credit Industries Inc.", - "Coventry Health Care Inc.", - "Cox Communications Inc.", - "Crane Co.", - "Crompton Corp.", - "Crown Cork & Seal Co. Inc.", - "CSK Auto Corp.", - "CSX Corp.", - "Cummins Inc.", - "CVS Corp.", - "Cytec Industries Inc.", - "D&K Healthcare Resources, Inc.", - "D.R. Horton Inc.", - "Dana Corporation", - "Danaher Corporation", - "Darden Restaurants Inc.", - "DaVita Inc.", - "Dean Foods Company", - "Deere & Company", - "Del Monte Foods Co", - "Dell Computer Corporation", - "Delphi Corp.", - "Delta Air Lines Inc.", - "Deluxe Corporation", - "Devon Energy Corporation", - "Di Giorgio Corporation", - "Dial Corporation", - "Diebold Incorporated", - "Dillard's Inc.", - "DIMON Incorporated", - "Dole Food Company, Inc.", - "Dollar General Corporation", - "Dollar Tree Stores, Inc.", - "Dominion Resources, Inc.", - "Domino's Pizza LLC", - "Dover Corporation, Inc.", - "Dow Chemical Company", - "Dow Jones & Company, Inc.", - "DPL Inc.", - "DQE Inc.", - "Dreyer's Grand Ice Cream, Inc.", - "DST Systems, Inc.", - "DTE Energy Co.", - "E.I. Du Pont de Nemours and Company", - "Duke Energy Corp", - "Dun & Bradstreet Inc.", - "DURA Automotive Systems Inc.", - "DynCorp", - "Dynegy Inc.", - "E*Trade Group, Inc.", - "E.W. Scripps Company", - "Earthlink, Inc.", - "Eastman Chemical Company", - "Eastman Kodak Company", - "Eaton Corporation", - "Echostar Communications Corporation", - "Ecolab Inc.", - "Edison International", - "EGL Inc.", - "El Paso Corporation", - "Electronic Arts Inc.", - "Electronic Data Systems Corp.", - "Eli Lilly and Company", - "EMC Corporation", - "Emcor Group Inc.", - "Emerson Electric Co.", - "Encompass Services Corporation", - "Energizer Holdings Inc.", - "Energy East Corporation", - "Engelhard Corporation", - "Enron Corp.", - "Entergy Corporation", - "Enterprise Products Partners L.P.", - "EOG Resources, Inc.", - "Equifax Inc.", - "Equitable Resources Inc.", - "Equity Office Properties Trust", - "Equity Residential Properties Trust", - "Estee Lauder Companies Inc.", - "Exelon Corporation", - "Exide Technologies", - "Expeditors International of Washington Inc.", - "Express Scripts Inc.", - "ExxonMobil Corporation", - "Fairchild Semiconductor International Inc.", - "Family Dollar Stores Inc.", - "Farmland Industries Inc.", - "Federal Mogul Corp.", - "Federated Department Stores Inc.", - "Federal Express Corp.", - "Felcor Lodging Trust Inc.", - "Ferro Corp.", - "Fidelity National Financial Inc.", - "Fifth Third Bancorp", - "First American Financial Corp.", - "First Data Corp.", - "First National of Nebraska Inc.", - "First Tennessee National Corp.", - "FirstEnergy Corp.", - "Fiserv Inc.", - "Fisher Scientific International Inc.", - "FleetBoston Financial Co.", - "Fleetwood Enterprises Inc.", - "Fleming Companies Inc.", - "Flowers Foods Inc.", - "Flowserv Corp", - "Fluor Corp", - "FMC Corp", - "Foamex International Inc", - "Foot Locker Inc", - "Footstar Inc.", - "Ford Motor Co", - "Forest Laboratories Inc.", - "Fortune Brands Inc.", - "Foster Wheeler Ltd.", - "FPL Group Inc.", - "Franklin Resources Inc.", - "Freeport McMoran Copper & Gold Inc.", - "Frontier Oil Corp", - "Furniture Brands International Inc.", - "Gannett Co., Inc.", - "Gap Inc.", - "Gateway Inc.", - "GATX Corporation", - "Gemstar-TV Guide International Inc.", - "GenCorp Inc.", - "General Cable Corporation", - "General Dynamics Corporation", - "General Electric Company", - "General Mills Inc", - "General Motors Corporation", - "Genesis Health Ventures Inc.", - "Gentek Inc.", - "Gentiva Health Services Inc.", - "Genuine Parts Company", - "Genuity Inc.", - "Genzyme Corporation", - "Georgia Gulf Corporation", - "Georgia-Pacific Corporation", - "Gillette Company", - "Gold Kist Inc.", - "Golden State Bancorp Inc.", - "Golden West Financial Corporation", - "Goldman Sachs Group Inc.", - "Goodrich Corporation", - "The Goodyear Tire & Rubber Company", - "Granite Construction Incorporated", - "Graybar Electric Company Inc.", - "Great Lakes Chemical Corporation", - "Great Plains Energy Inc.", - "GreenPoint Financial Corp.", - "Greif Bros. Corporation", - "Grey Global Group Inc.", - "Group 1 Automotive Inc.", - "Guidant Corporation", - "H&R Block Inc.", - "H.B. Fuller Company", - "H.J. Heinz Company", - "Halliburton Co.", - "Harley-Davidson Inc.", - "Harman International Industries Inc.", - "Harrah's Entertainment Inc.", - "Harris Corp.", - "Harsco Corp.", - "Hartford Financial Services Group Inc.", - "Hasbro Inc.", - "Hawaiian Electric Industries Inc.", - "HCA Inc.", - "Health Management Associates Inc.", - "Health Net Inc.", - "Healthsouth Corp", - "Henry Schein Inc.", - "Hercules Inc.", - "Herman Miller Inc.", - "Hershey Foods Corp.", - "Hewlett-Packard Company", - "Hibernia Corp.", - "Hillenbrand Industries Inc.", - "Hilton Hotels Corp.", - "Hollywood Entertainment Corp.", - "Home Depot Inc.", - "Hon Industries Inc.", - "Honeywell International Inc.", - "Hormel Foods Corp.", - "Host Marriott Corp.", - "Household International Corp.", - "Hovnanian Enterprises Inc.", - "Hub Group Inc.", - "Hubbell Inc.", - "Hughes Supply Inc.", - "Humana Inc.", - "Huntington Bancshares Inc.", - "Idacorp Inc.", - "IDT Corporation", - "IKON Office Solutions Inc.", - "Illinois Tool Works Inc.", - "IMC Global Inc.", - "Imperial Sugar Company", - "IMS Health Inc.", - "Ingles Market Inc", - "Ingram Micro Inc.", - "Insight Enterprises Inc.", - "Integrated Electrical Services Inc.", - "Intel Corporation", - "International Paper Co.", - "Interpublic Group of Companies Inc.", - "Interstate Bakeries Corporation", - "International Business Machines Corp.", - "International Flavors & Fragrances Inc.", - "International Multifoods Corporation", - "Intuit Inc.", - "IT Group Inc.", - "ITT Industries Inc.", - "Ivax Corp.", - "J.B. Hunt Transport Services Inc.", - "J.C. Penny Co.", - "J.P. Morgan Chase & Co.", - "Jabil Circuit Inc.", - "Jack In The Box Inc.", - "Jacobs Engineering Group Inc.", - "JDS Uniphase Corp.", - "Jefferson-Pilot Co.", - "John Hancock Financial Services Inc.", - "Johnson & Johnson", - "Johnson Controls Inc.", - "Jones Apparel Group Inc.", - "KB Home", - "Kellogg Company", - "Kellwood Company", - "Kelly Services Inc.", - "Kemet Corp.", - "Kennametal Inc.", - "Kerr-McGee Corporation", - "KeyCorp", - "KeySpan Corp.", - "Kimball International Inc.", - "Kimberly-Clark Corporation", - "Kindred Healthcare Inc.", - "KLA-Tencor Corporation", - "K-Mart Corp.", - "Knight-Ridder Inc.", - "Kohl's Corp.", - "KPMG Consulting Inc.", - "Kroger Co.", - "L-3 Communications Holdings Inc.", - "Laboratory Corporation of America Holdings", - "Lam Research Corporation", - "LandAmerica Financial Group Inc.", - "Lands' End Inc.", - "Landstar System Inc.", - "La-Z-Boy Inc.", - "Lear Corporation", - "Legg Mason Inc.", - "Leggett & Platt Inc.", - "Lehman Brothers Holdings Inc.", - "Lennar Corporation", - "Lennox International Inc.", - "Level 3 Communications Inc.", - "Levi Strauss & Co.", - "Lexmark International Inc.", - "Limited Inc.", - "Lincoln National Corporation", - "Linens 'n Things Inc.", - "Lithia Motors Inc.", - "Liz Claiborne Inc.", - "Lockheed Martin Corporation", - "Loews Corporation", - "Longs Drug Stores Corporation", - "Louisiana-Pacific Corporation", - "Lowe's Companies Inc.", - "LSI Logic Corporation", - "The LTV Corporation", - "The Lubrizol Corporation", - "Lucent Technologies Inc.", - "Lyondell Chemical Company", - "M & T Bank Corporation", - "Magellan Health Services Inc.", - "Mail-Well Inc.", - "Mandalay Resort Group", - "Manor Care Inc.", - "Manpower Inc.", - "Marathon Oil Corporation", - "Mariner Health Care Inc.", - "Markel Corporation", - "Marriott International Inc.", - "Marsh & McLennan Companies Inc.", - "Marsh Supermarkets Inc.", - "Marshall & Ilsley Corporation", - "Martin Marietta Materials Inc.", - "Masco Corporation", - "Massey Energy Company", - "MasTec Inc.", - "Mattel Inc.", - "Maxim Integrated Products Inc.", - "Maxtor Corporation", - "Maxxam Inc.", - "The May Department Stores Company", - "Maytag Corporation", - "MBNA Corporation", - "McCormick & Company Incorporated", - "McDonald's Corporation", - "The McGraw-Hill Companies Inc.", - "McKesson Corporation", - "McLeodUSA Incorporated", - "M.D.C. Holdings Inc.", - "MDU Resources Group Inc.", - "MeadWestvaco Corporation", - "Medtronic Inc.", - "Mellon Financial Corporation", - "The Men's Wearhouse Inc.", - "Merck & Co., Inc.", - "Mercury General Corporation", - "Merrill Lynch & Co. Inc.", - "Metaldyne Corporation", - "Metals USA Inc.", - "MetLife Inc.", - "Metris Companies Inc", - "MGIC Investment Corporation", - "MGM Mirage", - "Michaels Stores Inc.", - "Micron Technology Inc.", - "Microsoft Corporation", - "Milacron Inc.", - "Millennium Chemicals Inc.", - "Mirant Corporation", - "Mohawk Industries Inc.", - "Molex Incorporated", - "The MONY Group Inc.", - "Morgan Stanley Dean Witter & Co.", - "Motorola Inc.", - "MPS Group Inc.", - "Murphy Oil Corporation", - "Nabors Industries Inc", - "Nacco Industries Inc", - "Nash Finch Company", - "National City Corp.", - "National Commerce Financial Corporation", - "National Fuel Gas Company", - "National Oilwell Inc", - "National Rural Utilities Cooperative Finance Corporation", - "National Semiconductor Corporation", - "National Service Industries Inc", - "Navistar International Corporation", - "NCR Corporation", - "The Neiman Marcus Group Inc.", - "New Jersey Resources Corporation", - "New York Times Company", - "Newell Rubbermaid Inc", - "Newmont Mining Corporation", - "Nextel Communications Inc", - "Nicor Inc", - "Nike Inc", - "NiSource Inc", - "Noble Energy Inc", - "Nordstrom Inc", - "Norfolk Southern Corporation", - "Nortek Inc", - "North Fork Bancorporation Inc", - "Northeast Utilities System", - "Northern Trust Corporation", - "Northrop Grumman Corporation", - "NorthWestern Corporation", - "Novellus Systems Inc", - "NSTAR", - "NTL Incorporated", - "Nucor Corp", - "Nvidia Corp", - "NVR Inc", - "Northwest Airlines Corp", - "Occidental Petroleum Corp", - "Ocean Energy Inc", - "Office Depot Inc.", - "OfficeMax Inc", - "OGE Energy Corp", - "Oglethorpe Power Corp.", - "Ohio Casualty Corp.", - "Old Republic International Corp.", - "Olin Corp.", - "OM Group Inc", - "Omnicare Inc", - "Omnicom Group", - "On Semiconductor Corp", - "ONEOK Inc", - "Oracle Corp", - "Oshkosh Truck Corp", - "Outback Steakhouse Inc.", - "Owens & Minor Inc.", - "Owens Corning", - "Owens-Illinois Inc", - "Oxford Health Plans Inc", - "Paccar Inc", - "PacifiCare Health Systems Inc", - "Packaging Corp. of America", - "Pactiv Corp", - "Pall Corp", - "Pantry Inc", - "Park Place Entertainment Corp", - "Parker Hannifin Corp.", - "Pathmark Stores Inc.", - "Paychex Inc", - "Payless Shoesource Inc", - "Penn Traffic Co.", - "Pennzoil-Quaker State Company", - "Pentair Inc", - "Peoples Energy Corp.", - "PeopleSoft Inc", - "Pep Boys Manny, Moe & Jack", - "Potomac Electric Power Co.", - "Pepsi Bottling Group Inc.", - "PepsiAmericas Inc.", - "PepsiCo Inc.", - "Performance Food Group Co.", - "Perini Corp", - "PerkinElmer Inc", - "Perot Systems Corp", - "Petco Animal Supplies Inc.", - "Peter Kiewit Sons', Inc.", - "PETsMART Inc", - "Pfizer Inc", - "Pacific Gas & Electric Corp.", - "Pharmacia Corp", - "Phar Mor Inc.", - "Phelps Dodge Corp.", - "Philip Morris Companies Inc.", - "Phillips Petroleum Co", - "Phillips Van Heusen Corp.", - "Phoenix Companies Inc", - "Pier 1 Imports Inc.", - "Pilgrim's Pride Corporation", - "Pinnacle West Capital Corp", - "Pioneer-Standard Electronics Inc.", - "Pitney Bowes Inc.", - "Pittston Brinks Group", - "Plains All American Pipeline LP", - "PNC Financial Services Group Inc.", - "PNM Resources Inc", - "Polaris Industries Inc.", - "Polo Ralph Lauren Corp", - "PolyOne Corp", - "Popular Inc", - "Potlatch Corp", - "PPG Industries Inc", - "PPL Corp", - "Praxair Inc", - "Precision Castparts Corp", - "Premcor Inc.", - "Pride International Inc", - "Primedia Inc", - "Principal Financial Group Inc.", - "Procter & Gamble Co.", - "Pro-Fac Cooperative Inc.", - "Progress Energy Inc", - "Progressive Corporation", - "Protective Life Corp", - "Provident Financial Group", - "Providian Financial Corp.", - "Prudential Financial Inc.", - "PSS World Medical Inc", - "Public Service Enterprise Group Inc.", - "Publix Super Markets Inc.", - "Puget Energy Inc.", - "Pulte Homes Inc", - "Qualcomm Inc", - "Quanta Services Inc.", - "Quantum Corp", - "Quest Diagnostics Inc.", - "Questar Corp", - "Quintiles Transnational", - "Qwest Communications Intl Inc", - "R.J. Reynolds Tobacco Company", - "R.R. Donnelley & Sons Company", - "Radio Shack Corporation", - "Raymond James Financial Inc.", - "Raytheon Company", - "Reader's Digest Association Inc.", - "Reebok International Ltd.", - "Regions Financial Corp.", - "Regis Corporation", - "Reliance Steel & Aluminum Co.", - "Reliant Energy Inc.", - "Rent A Center Inc", - "Republic Services Inc", - "Revlon Inc", - "RGS Energy Group Inc", - "Rite Aid Corp", - "Riverwood Holding Inc.", - "RoadwayCorp", - "Robert Half International Inc.", - "Rock-Tenn Co", - "Rockwell Automation Inc", - "Rockwell Collins Inc", - "Rohm & Haas Co.", - "Ross Stores Inc", - "RPM Inc.", - "Ruddick Corp", - "Ryder System Inc", - "Ryerson Tull Inc", - "Ryland Group Inc.", - "Sabre Holdings Corp", - "Safeco Corp", - "Safeguard Scientifics Inc.", - "Safeway Inc", - "Saks Inc", - "Sanmina-SCI Inc", - "Sara Lee Corp", - "SBC Communications Inc", - "Scana Corp.", - "Schering-Plough Corp", - "Scholastic Corp", - "SCI Systems Onc.", - "Science Applications Intl. Inc.", - "Scientific-Atlanta Inc", - "Scotts Company", - "Seaboard Corp", - "Sealed Air Corp", - "Sears Roebuck & Co", - "Sempra Energy", - "Sequa Corp", - "Service Corp. International", - "ServiceMaster Co", - "Shaw Group Inc", - "Sherwin-Williams Company", - "Shopko Stores Inc", - "Siebel Systems Inc", - "Sierra Health Services Inc", - "Sierra Pacific Resources", - "Silgan Holdings Inc.", - "Silicon Graphics Inc", - "Simon Property Group Inc", - "SLM Corporation", - "Smith International Inc", - "Smithfield Foods Inc", - "Smurfit-Stone Container Corp", - "Snap-On Inc", - "Solectron Corp", - "Solutia Inc", - "Sonic Automotive Inc.", - "Sonoco Products Co.", - "Southern Company", - "Southern Union Company", - "SouthTrust Corp.", - "Southwest Airlines Co", - "Southwest Gas Corp", - "Sovereign Bancorp Inc.", - "Spartan Stores Inc", - "Spherion Corp", - "Sports Authority Inc", - "Sprint Corp.", - "SPX Corp", - "St. Jude Medical Inc", - "St. Paul Cos.", - "Staff Leasing Inc.", - "StanCorp Financial Group Inc", - "Standard Pacific Corp.", - "Stanley Works", - "Staples Inc", - "Starbucks Corp", - "Starwood Hotels & Resorts Worldwide Inc", - "State Street Corp.", - "Stater Bros. Holdings Inc.", - "Steelcase Inc", - "Stein Mart Inc", - "Stewart & Stevenson Services Inc", - "Stewart Information Services Corp", - "Stilwell Financial Inc", - "Storage Technology Corporation", - "Stryker Corp", - "Sun Healthcare Group Inc.", - "Sun Microsystems Inc.", - "SunGard Data Systems Inc.", - "Sunoco Inc.", - "SunTrust Banks Inc", - "Supervalu Inc", - "Swift Transportation, Co., Inc", - "Symbol Technologies Inc", - "Synovus Financial Corp.", - "Sysco Corp", - "Systemax Inc.", - "Target Corp.", - "Tech Data Corporation", - "TECO Energy Inc", - "Tecumseh Products Company", - "Tektronix Inc", - "Teleflex Incorporated", - "Telephone & Data Systems Inc", - "Tellabs Inc.", - "Temple-Inland Inc", - "Tenet Healthcare Corporation", - "Tenneco Automotive Inc.", - "Teradyne Inc", - "Terex Corp", - "Tesoro Petroleum Corp.", - "Texas Industries Inc.", - "Texas Instruments Incorporated", - "Textron Inc", - "Thermo Electron Corporation", - "Thomas & Betts Corporation", - "Tiffany & Co", - "Timken Company", - "TJX Companies Inc", - "TMP Worldwide Inc", - "Toll Brothers Inc", - "Torchmark Corporation", - "Toro Company", - "Tower Automotive Inc.", - "Toys 'R' Us Inc", - "Trans World Entertainment Corp.", - "TransMontaigne Inc", - "Transocean Inc", - "TravelCenters of America Inc.", - "Triad Hospitals Inc", - "Tribune Company", - "Trigon Healthcare Inc.", - "Trinity Industries Inc", - "Trump Hotels & Casino Resorts Inc.", - "TruServ Corporation", - "TRW Inc", - "TXU Corp", - "Tyson Foods Inc", - "U.S. Bancorp", - "U.S. Industries Inc.", - "UAL Corporation", - "UGI Corporation", - "Unified Western Grocers Inc", - "Union Pacific Corporation", - "Union Planters Corp", - "Unisource Energy Corp", - "Unisys Corporation", - "United Auto Group Inc", - "United Defense Industries Inc.", - "United Parcel Service Inc", - "United Rentals Inc", - "United Stationers Inc", - "United Technologies Corporation", - "UnitedHealth Group Incorporated", - "Unitrin Inc", - "Universal Corporation", - "Universal Forest Products Inc", - "Universal Health Services Inc", - "Unocal Corporation", - "Unova Inc", - "UnumProvident Corporation", - "URS Corporation", - "US Airways Group Inc", - "US Oncology Inc", - "USA Interactive", - "USFreighways Corporation", - "USG Corporation", - "UST Inc", - "Valero Energy Corporation", - "Valspar Corporation", - "Value City Department Stores Inc", - "Varco International Inc", - "Vectren Corporation", - "Veritas Software Corporation", - "Verizon Communications Inc", - "VF Corporation", - "Viacom Inc", - "Viad Corp", - "Viasystems Group Inc", - "Vishay Intertechnology Inc", - "Visteon Corporation", - "Volt Information Sciences Inc", - "Vulcan Materials Company", - "W.R. Berkley Corporation", - "W.R. Grace & Co", - "W.W. Grainger Inc", - "Wachovia Corporation", - "Wakenhut Corporation", - "Walgreen Co", - "Wallace Computer Services Inc", - "Wal-Mart Stores Inc", - "Walt Disney Co", - "Walter Industries Inc", - "Washington Mutual Inc", - "Washington Post Co.", - "Waste Management Inc", - "Watsco Inc", - "Weatherford International Inc", - "Weis Markets Inc.", - "Wellpoint Health Networks Inc", - "Wells Fargo & Company", - "Wendy's International Inc", - "Werner Enterprises Inc", - "WESCO International Inc", - "Western Digital Inc", - "Western Gas Resources Inc", - "WestPoint Stevens Inc", - "Weyerhauser Company", - "WGL Holdings Inc", - "Whirlpool Corporation", - "Whole Foods Market Inc", - "Willamette Industries Inc.", - "Williams Companies Inc", - "Williams Sonoma Inc", - "Winn Dixie Stores Inc", - "Wisconsin Energy Corporation", - "Wm Wrigley Jr Company", - "World Fuel Services Corporation", - "WorldCom Inc", - "Worthington Industries Inc", - "WPS Resources Corporation", - "Wyeth", - "Wyndham International Inc", - "Xcel Energy Inc", - "Xerox Corp", - "Xilinx Inc", - "XO Communications Inc", - "Yellow Corporation", - "York International Corp", - "Yum Brands Inc.", - "Zale Corporation", - "Zions Bancorporation" - ], - - fileExtension : { - "raster" : ["bmp", "gif", "gpl", "ico", "jpeg", "psd", "png", "psp", "raw", "tiff"], - "vector" : ["3dv", "amf", "awg", "ai", "cgm", "cdr", "cmx", "dxf", "e2d", "egt", "eps", "fs", "odg", "svg", "xar"], - "3d" : ["3dmf", "3dm", "3mf", "3ds", "an8", "aoi", "blend", "cal3d", "cob", "ctm", "iob", "jas", "max", "mb", "mdx", "obj", "x", "x3d"], - "document" : ["doc", "docx", "dot", "html", "xml", "odt", "odm", "ott", "csv", "rtf", "tex", "xhtml", "xps"] - }, - - // Data taken from https://github.com/dmfilipenko/timezones.json/blob/master/timezones.json - timezones: [ - { - "name": "Dateline Standard Time", - "abbr": "DST", - "offset": -12, - "isdst": false, - "text": "(UTC-12:00) International Date Line West", - "utc": [ - "Etc/GMT+12" - ] - }, - { - "name": "UTC-11", - "abbr": "U", - "offset": -11, - "isdst": false, - "text": "(UTC-11:00) Coordinated Universal Time-11", - "utc": [ - "Etc/GMT+11", - "Pacific/Midway", - "Pacific/Niue", - "Pacific/Pago_Pago" - ] - }, - { - "name": "Hawaiian Standard Time", - "abbr": "HST", - "offset": -10, - "isdst": false, - "text": "(UTC-10:00) Hawaii", - "utc": [ - "Etc/GMT+10", - "Pacific/Honolulu", - "Pacific/Johnston", - "Pacific/Rarotonga", - "Pacific/Tahiti" - ] - }, - { - "name": "Alaskan Standard Time", - "abbr": "AKDT", - "offset": -8, - "isdst": true, - "text": "(UTC-09:00) Alaska", - "utc": [ - "America/Anchorage", - "America/Juneau", - "America/Nome", - "America/Sitka", - "America/Yakutat" - ] - }, - { - "name": "Pacific Standard Time (Mexico)", - "abbr": "PDT", - "offset": -7, - "isdst": true, - "text": "(UTC-08:00) Baja California", - "utc": [ - "America/Santa_Isabel" - ] - }, - { - "name": "Pacific Daylight Time", - "abbr": "PDT", - "offset": -7, - "isdst": true, - "text": "(UTC-07:00) Pacific Time (US & Canada)", - "utc": [ - "America/Dawson", - "America/Los_Angeles", - "America/Tijuana", - "America/Vancouver", - "America/Whitehorse" - ] - }, - { - "name": "Pacific Standard Time", - "abbr": "PST", - "offset": -8, - "isdst": false, - "text": "(UTC-08:00) Pacific Time (US & Canada)", - "utc": [ - "America/Dawson", - "America/Los_Angeles", - "America/Tijuana", - "America/Vancouver", - "America/Whitehorse", - "PST8PDT" - ] - }, - { - "name": "US Mountain Standard Time", - "abbr": "UMST", - "offset": -7, - "isdst": false, - "text": "(UTC-07:00) Arizona", - "utc": [ - "America/Creston", - "America/Dawson_Creek", - "America/Hermosillo", - "America/Phoenix", - "Etc/GMT+7" - ] - }, - { - "name": "Mountain Standard Time (Mexico)", - "abbr": "MDT", - "offset": -6, - "isdst": true, - "text": "(UTC-07:00) Chihuahua, La Paz, Mazatlan", - "utc": [ - "America/Chihuahua", - "America/Mazatlan" - ] - }, - { - "name": "Mountain Standard Time", - "abbr": "MDT", - "offset": -6, - "isdst": true, - "text": "(UTC-07:00) Mountain Time (US & Canada)", - "utc": [ - "America/Boise", - "America/Cambridge_Bay", - "America/Denver", - "America/Edmonton", - "America/Inuvik", - "America/Ojinaga", - "America/Yellowknife", - "MST7MDT" - ] - }, - { - "name": "Central America Standard Time", - "abbr": "CAST", - "offset": -6, - "isdst": false, - "text": "(UTC-06:00) Central America", - "utc": [ - "America/Belize", - "America/Costa_Rica", - "America/El_Salvador", - "America/Guatemala", - "America/Managua", - "America/Tegucigalpa", - "Etc/GMT+6", - "Pacific/Galapagos" - ] - }, - { - "name": "Central Standard Time", - "abbr": "CDT", - "offset": -5, - "isdst": true, - "text": "(UTC-06:00) Central Time (US & Canada)", - "utc": [ - "America/Chicago", - "America/Indiana/Knox", - "America/Indiana/Tell_City", - "America/Matamoros", - "America/Menominee", - "America/North_Dakota/Beulah", - "America/North_Dakota/Center", - "America/North_Dakota/New_Salem", - "America/Rainy_River", - "America/Rankin_Inlet", - "America/Resolute", - "America/Winnipeg", - "CST6CDT" - ] - }, - { - "name": "Central Standard Time (Mexico)", - "abbr": "CDT", - "offset": -5, - "isdst": true, - "text": "(UTC-06:00) Guadalajara, Mexico City, Monterrey", - "utc": [ - "America/Bahia_Banderas", - "America/Cancun", - "America/Merida", - "America/Mexico_City", - "America/Monterrey" - ] - }, - { - "name": "Canada Central Standard Time", - "abbr": "CCST", - "offset": -6, - "isdst": false, - "text": "(UTC-06:00) Saskatchewan", - "utc": [ - "America/Regina", - "America/Swift_Current" - ] - }, - { - "name": "SA Pacific Standard Time", - "abbr": "SPST", - "offset": -5, - "isdst": false, - "text": "(UTC-05:00) Bogota, Lima, Quito", - "utc": [ - "America/Bogota", - "America/Cayman", - "America/Coral_Harbour", - "America/Eirunepe", - "America/Guayaquil", - "America/Jamaica", - "America/Lima", - "America/Panama", - "America/Rio_Branco", - "Etc/GMT+5" - ] - }, - { - "name": "Eastern Standard Time", - "abbr": "EDT", - "offset": -4, - "isdst": true, - "text": "(UTC-05:00) Eastern Time (US & Canada)", - "utc": [ - "America/Detroit", - "America/Havana", - "America/Indiana/Petersburg", - "America/Indiana/Vincennes", - "America/Indiana/Winamac", - "America/Iqaluit", - "America/Kentucky/Monticello", - "America/Louisville", - "America/Montreal", - "America/Nassau", - "America/New_York", - "America/Nipigon", - "America/Pangnirtung", - "America/Port-au-Prince", - "America/Thunder_Bay", - "America/Toronto", - "EST5EDT" - ] - }, - { - "name": "US Eastern Standard Time", - "abbr": "UEDT", - "offset": -4, - "isdst": true, - "text": "(UTC-05:00) Indiana (East)", - "utc": [ - "America/Indiana/Marengo", - "America/Indiana/Vevay", - "America/Indianapolis" - ] - }, - { - "name": "Venezuela Standard Time", - "abbr": "VST", - "offset": -4.5, - "isdst": false, - "text": "(UTC-04:30) Caracas", - "utc": [ - "America/Caracas" - ] - }, - { - "name": "Paraguay Standard Time", - "abbr": "PYT", - "offset": -4, - "isdst": false, - "text": "(UTC-04:00) Asuncion", - "utc": [ - "America/Asuncion" - ] - }, - { - "name": "Atlantic Standard Time", - "abbr": "ADT", - "offset": -3, - "isdst": true, - "text": "(UTC-04:00) Atlantic Time (Canada)", - "utc": [ - "America/Glace_Bay", - "America/Goose_Bay", - "America/Halifax", - "America/Moncton", - "America/Thule", - "Atlantic/Bermuda" - ] - }, - { - "name": "Central Brazilian Standard Time", - "abbr": "CBST", - "offset": -4, - "isdst": false, - "text": "(UTC-04:00) Cuiaba", - "utc": [ - "America/Campo_Grande", - "America/Cuiaba" - ] - }, - { - "name": "SA Western Standard Time", - "abbr": "SWST", - "offset": -4, - "isdst": false, - "text": "(UTC-04:00) Georgetown, La Paz, Manaus, San Juan", - "utc": [ - "America/Anguilla", - "America/Antigua", - "America/Aruba", - "America/Barbados", - "America/Blanc-Sablon", - "America/Boa_Vista", - "America/Curacao", - "America/Dominica", - "America/Grand_Turk", - "America/Grenada", - "America/Guadeloupe", - "America/Guyana", - "America/Kralendijk", - "America/La_Paz", - "America/Lower_Princes", - "America/Manaus", - "America/Marigot", - "America/Martinique", - "America/Montserrat", - "America/Port_of_Spain", - "America/Porto_Velho", - "America/Puerto_Rico", - "America/Santo_Domingo", - "America/St_Barthelemy", - "America/St_Kitts", - "America/St_Lucia", - "America/St_Thomas", - "America/St_Vincent", - "America/Tortola", - "Etc/GMT+4" - ] - }, - { - "name": "Pacific SA Standard Time", - "abbr": "PSST", - "offset": -4, - "isdst": false, - "text": "(UTC-04:00) Santiago", - "utc": [ - "America/Santiago", - "Antarctica/Palmer" - ] - }, - { - "name": "Newfoundland Standard Time", - "abbr": "NDT", - "offset": -2.5, - "isdst": true, - "text": "(UTC-03:30) Newfoundland", - "utc": [ - "America/St_Johns" - ] - }, - { - "name": "E. South America Standard Time", - "abbr": "ESAST", - "offset": -3, - "isdst": false, - "text": "(UTC-03:00) Brasilia", - "utc": [ - "America/Sao_Paulo" - ] - }, - { - "name": "Argentina Standard Time", - "abbr": "AST", - "offset": -3, - "isdst": false, - "text": "(UTC-03:00) Buenos Aires", - "utc": [ - "America/Argentina/La_Rioja", - "America/Argentina/Rio_Gallegos", - "America/Argentina/Salta", - "America/Argentina/San_Juan", - "America/Argentina/San_Luis", - "America/Argentina/Tucuman", - "America/Argentina/Ushuaia", - "America/Buenos_Aires", - "America/Catamarca", - "America/Cordoba", - "America/Jujuy", - "America/Mendoza" - ] - }, - { - "name": "SA Eastern Standard Time", - "abbr": "SEST", - "offset": -3, - "isdst": false, - "text": "(UTC-03:00) Cayenne, Fortaleza", - "utc": [ - "America/Araguaina", - "America/Belem", - "America/Cayenne", - "America/Fortaleza", - "America/Maceio", - "America/Paramaribo", - "America/Recife", - "America/Santarem", - "Antarctica/Rothera", - "Atlantic/Stanley", - "Etc/GMT+3" - ] - }, - { - "name": "Greenland Standard Time", - "abbr": "GDT", - "offset": -3, - "isdst": true, - "text": "(UTC-03:00) Greenland", - "utc": [ - "America/Godthab" - ] - }, - { - "name": "Montevideo Standard Time", - "abbr": "MST", - "offset": -3, - "isdst": false, - "text": "(UTC-03:00) Montevideo", - "utc": [ - "America/Montevideo" - ] - }, - { - "name": "Bahia Standard Time", - "abbr": "BST", - "offset": -3, - "isdst": false, - "text": "(UTC-03:00) Salvador", - "utc": [ - "America/Bahia" - ] - }, - { - "name": "UTC-02", - "abbr": "U", - "offset": -2, - "isdst": false, - "text": "(UTC-02:00) Coordinated Universal Time-02", - "utc": [ - "America/Noronha", - "Atlantic/South_Georgia", - "Etc/GMT+2" - ] - }, - { - "name": "Mid-Atlantic Standard Time", - "abbr": "MDT", - "offset": -1, - "isdst": true, - "text": "(UTC-02:00) Mid-Atlantic - Old", - "utc": [] - }, - { - "name": "Azores Standard Time", - "abbr": "ADT", - "offset": 0, - "isdst": true, - "text": "(UTC-01:00) Azores", - "utc": [ - "America/Scoresbysund", - "Atlantic/Azores" - ] - }, - { - "name": "Cape Verde Standard Time", - "abbr": "CVST", - "offset": -1, - "isdst": false, - "text": "(UTC-01:00) Cape Verde Is.", - "utc": [ - "Atlantic/Cape_Verde", - "Etc/GMT+1" - ] - }, - { - "name": "Morocco Standard Time", - "abbr": "MDT", - "offset": 1, - "isdst": true, - "text": "(UTC) Casablanca", - "utc": [ - "Africa/Casablanca", - "Africa/El_Aaiun" - ] - }, - { - "name": "UTC", - "abbr": "UTC", - "offset": 0, - "isdst": false, - "text": "(UTC) Coordinated Universal Time", - "utc": [ - "America/Danmarkshavn", - "Etc/GMT" - ] - }, - { - "name": "GMT Standard Time", - "abbr": "GMT", - "offset": 0, - "isdst": false, - "text": "(UTC) Edinburgh, London", - "utc": [ - "Europe/Isle_of_Man", - "Europe/Guernsey", - "Europe/Jersey", - "Europe/London" - ] - }, - { - "name": "British Summer Time", - "abbr": "BST", - "offset": 1, - "isdst": true, - "text": "(UTC+01:00) Edinburgh, London", - "utc": [ - "Europe/Isle_of_Man", - "Europe/Guernsey", - "Europe/Jersey", - "Europe/London" - ] - }, - { - "name": "GMT Standard Time", - "abbr": "GDT", - "offset": 1, - "isdst": true, - "text": "(UTC) Dublin, Lisbon", - "utc": [ - "Atlantic/Canary", - "Atlantic/Faeroe", - "Atlantic/Madeira", - "Europe/Dublin", - "Europe/Lisbon" - ] - }, - { - "name": "Greenwich Standard Time", - "abbr": "GST", - "offset": 0, - "isdst": false, - "text": "(UTC) Monrovia, Reykjavik", - "utc": [ - "Africa/Abidjan", - "Africa/Accra", - "Africa/Bamako", - "Africa/Banjul", - "Africa/Bissau", - "Africa/Conakry", - "Africa/Dakar", - "Africa/Freetown", - "Africa/Lome", - "Africa/Monrovia", - "Africa/Nouakchott", - "Africa/Ouagadougou", - "Africa/Sao_Tome", - "Atlantic/Reykjavik", - "Atlantic/St_Helena" - ] - }, - { - "name": "W. Europe Standard Time", - "abbr": "WEDT", - "offset": 2, - "isdst": true, - "text": "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna", - "utc": [ - "Arctic/Longyearbyen", - "Europe/Amsterdam", - "Europe/Andorra", - "Europe/Berlin", - "Europe/Busingen", - "Europe/Gibraltar", - "Europe/Luxembourg", - "Europe/Malta", - "Europe/Monaco", - "Europe/Oslo", - "Europe/Rome", - "Europe/San_Marino", - "Europe/Stockholm", - "Europe/Vaduz", - "Europe/Vatican", - "Europe/Vienna", - "Europe/Zurich" - ] - }, - { - "name": "Central Europe Standard Time", - "abbr": "CEDT", - "offset": 2, - "isdst": true, - "text": "(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague", - "utc": [ - "Europe/Belgrade", - "Europe/Bratislava", - "Europe/Budapest", - "Europe/Ljubljana", - "Europe/Podgorica", - "Europe/Prague", - "Europe/Tirane" - ] - }, - { - "name": "Romance Standard Time", - "abbr": "RDT", - "offset": 2, - "isdst": true, - "text": "(UTC+01:00) Brussels, Copenhagen, Madrid, Paris", - "utc": [ - "Africa/Ceuta", - "Europe/Brussels", - "Europe/Copenhagen", - "Europe/Madrid", - "Europe/Paris" - ] - }, - { - "name": "Central European Standard Time", - "abbr": "CEDT", - "offset": 2, - "isdst": true, - "text": "(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb", - "utc": [ - "Europe/Sarajevo", - "Europe/Skopje", - "Europe/Warsaw", - "Europe/Zagreb" - ] - }, - { - "name": "W. Central Africa Standard Time", - "abbr": "WCAST", - "offset": 1, - "isdst": false, - "text": "(UTC+01:00) West Central Africa", - "utc": [ - "Africa/Algiers", - "Africa/Bangui", - "Africa/Brazzaville", - "Africa/Douala", - "Africa/Kinshasa", - "Africa/Lagos", - "Africa/Libreville", - "Africa/Luanda", - "Africa/Malabo", - "Africa/Ndjamena", - "Africa/Niamey", - "Africa/Porto-Novo", - "Africa/Tunis", - "Etc/GMT-1" - ] - }, - { - "name": "Namibia Standard Time", - "abbr": "NST", - "offset": 1, - "isdst": false, - "text": "(UTC+01:00) Windhoek", - "utc": [ - "Africa/Windhoek" - ] - }, - { - "name": "GTB Standard Time", - "abbr": "GDT", - "offset": 3, - "isdst": true, - "text": "(UTC+02:00) Athens, Bucharest", - "utc": [ - "Asia/Nicosia", - "Europe/Athens", - "Europe/Bucharest", - "Europe/Chisinau" - ] - }, - { - "name": "Middle East Standard Time", - "abbr": "MEDT", - "offset": 3, - "isdst": true, - "text": "(UTC+02:00) Beirut", - "utc": [ - "Asia/Beirut" - ] - }, - { - "name": "Egypt Standard Time", - "abbr": "EST", - "offset": 2, - "isdst": false, - "text": "(UTC+02:00) Cairo", - "utc": [ - "Africa/Cairo" - ] - }, - { - "name": "Syria Standard Time", - "abbr": "SDT", - "offset": 3, - "isdst": true, - "text": "(UTC+02:00) Damascus", - "utc": [ - "Asia/Damascus" - ] - }, - { - "name": "E. Europe Standard Time", - "abbr": "EEDT", - "offset": 3, - "isdst": true, - "text": "(UTC+02:00) E. Europe", - "utc": [ - "Asia/Nicosia", - "Europe/Athens", - "Europe/Bucharest", - "Europe/Chisinau", - "Europe/Helsinki", - "Europe/Kiev", - "Europe/Mariehamn", - "Europe/Nicosia", - "Europe/Riga", - "Europe/Sofia", - "Europe/Tallinn", - "Europe/Uzhgorod", - "Europe/Vilnius", - "Europe/Zaporozhye" - ] - }, - { - "name": "South Africa Standard Time", - "abbr": "SAST", - "offset": 2, - "isdst": false, - "text": "(UTC+02:00) Harare, Pretoria", - "utc": [ - "Africa/Blantyre", - "Africa/Bujumbura", - "Africa/Gaborone", - "Africa/Harare", - "Africa/Johannesburg", - "Africa/Kigali", - "Africa/Lubumbashi", - "Africa/Lusaka", - "Africa/Maputo", - "Africa/Maseru", - "Africa/Mbabane", - "Etc/GMT-2" - ] - }, - { - "name": "FLE Standard Time", - "abbr": "FDT", - "offset": 3, - "isdst": true, - "text": "(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius", - "utc": [ - "Europe/Helsinki", - "Europe/Kiev", - "Europe/Mariehamn", - "Europe/Riga", - "Europe/Sofia", - "Europe/Tallinn", - "Europe/Uzhgorod", - "Europe/Vilnius", - "Europe/Zaporozhye" - ] - }, - { - "name": "Turkey Standard Time", - "abbr": "TDT", - "offset": 3, - "isdst": false, - "text": "(UTC+03:00) Istanbul", - "utc": [ - "Europe/Istanbul" - ] - }, - { - "name": "Israel Standard Time", - "abbr": "JDT", - "offset": 3, - "isdst": true, - "text": "(UTC+02:00) Jerusalem", - "utc": [ - "Asia/Jerusalem" - ] - }, - { - "name": "Libya Standard Time", - "abbr": "LST", - "offset": 2, - "isdst": false, - "text": "(UTC+02:00) Tripoli", - "utc": [ - "Africa/Tripoli" - ] - }, - { - "name": "Jordan Standard Time", - "abbr": "JST", - "offset": 3, - "isdst": false, - "text": "(UTC+03:00) Amman", - "utc": [ - "Asia/Amman" - ] - }, - { - "name": "Arabic Standard Time", - "abbr": "AST", - "offset": 3, - "isdst": false, - "text": "(UTC+03:00) Baghdad", - "utc": [ - "Asia/Baghdad" - ] - }, - { - "name": "Kaliningrad Standard Time", - "abbr": "KST", - "offset": 3, - "isdst": false, - "text": "(UTC+02:00) Kaliningrad", - "utc": [ - "Europe/Kaliningrad" - ] - }, - { - "name": "Arab Standard Time", - "abbr": "AST", - "offset": 3, - "isdst": false, - "text": "(UTC+03:00) Kuwait, Riyadh", - "utc": [ - "Asia/Aden", - "Asia/Bahrain", - "Asia/Kuwait", - "Asia/Qatar", - "Asia/Riyadh" - ] - }, - { - "name": "E. Africa Standard Time", - "abbr": "EAST", - "offset": 3, - "isdst": false, - "text": "(UTC+03:00) Nairobi", - "utc": [ - "Africa/Addis_Ababa", - "Africa/Asmera", - "Africa/Dar_es_Salaam", - "Africa/Djibouti", - "Africa/Juba", - "Africa/Kampala", - "Africa/Khartoum", - "Africa/Mogadishu", - "Africa/Nairobi", - "Antarctica/Syowa", - "Etc/GMT-3", - "Indian/Antananarivo", - "Indian/Comoro", - "Indian/Mayotte" - ] - }, - { - "name": "Moscow Standard Time", - "abbr": "MSK", - "offset": 3, - "isdst": false, - "text": "(UTC+03:00) Moscow, St. Petersburg, Volgograd, Minsk", - "utc": [ - "Europe/Kirov", - "Europe/Moscow", - "Europe/Simferopol", - "Europe/Volgograd", - "Europe/Minsk" - ] - }, - { - "name": "Samara Time", - "abbr": "SAMT", - "offset": 4, - "isdst": false, - "text": "(UTC+04:00) Samara, Ulyanovsk, Saratov", - "utc": [ - "Europe/Astrakhan", - "Europe/Samara", - "Europe/Ulyanovsk" - ] - }, - { - "name": "Iran Standard Time", - "abbr": "IDT", - "offset": 4.5, - "isdst": true, - "text": "(UTC+03:30) Tehran", - "utc": [ - "Asia/Tehran" - ] - }, - { - "name": "Arabian Standard Time", - "abbr": "AST", - "offset": 4, - "isdst": false, - "text": "(UTC+04:00) Abu Dhabi, Muscat", - "utc": [ - "Asia/Dubai", - "Asia/Muscat", - "Etc/GMT-4" - ] - }, - { - "name": "Azerbaijan Standard Time", - "abbr": "ADT", - "offset": 5, - "isdst": true, - "text": "(UTC+04:00) Baku", - "utc": [ - "Asia/Baku" - ] - }, - { - "name": "Mauritius Standard Time", - "abbr": "MST", - "offset": 4, - "isdst": false, - "text": "(UTC+04:00) Port Louis", - "utc": [ - "Indian/Mahe", - "Indian/Mauritius", - "Indian/Reunion" - ] - }, - { - "name": "Georgian Standard Time", - "abbr": "GET", - "offset": 4, - "isdst": false, - "text": "(UTC+04:00) Tbilisi", - "utc": [ - "Asia/Tbilisi" - ] - }, - { - "name": "Caucasus Standard Time", - "abbr": "CST", - "offset": 4, - "isdst": false, - "text": "(UTC+04:00) Yerevan", - "utc": [ - "Asia/Yerevan" - ] - }, - { - "name": "Afghanistan Standard Time", - "abbr": "AST", - "offset": 4.5, - "isdst": false, - "text": "(UTC+04:30) Kabul", - "utc": [ - "Asia/Kabul" - ] - }, - { - "name": "West Asia Standard Time", - "abbr": "WAST", - "offset": 5, - "isdst": false, - "text": "(UTC+05:00) Ashgabat, Tashkent", - "utc": [ - "Antarctica/Mawson", - "Asia/Aqtau", - "Asia/Aqtobe", - "Asia/Ashgabat", - "Asia/Dushanbe", - "Asia/Oral", - "Asia/Samarkand", - "Asia/Tashkent", - "Etc/GMT-5", - "Indian/Kerguelen", - "Indian/Maldives" - ] - }, - { - "name": "Yekaterinburg Time", - "abbr": "YEKT", - "offset": 5, - "isdst": false, - "text": "(UTC+05:00) Yekaterinburg", - "utc": [ - "Asia/Yekaterinburg" - ] - }, - { - "name": "Pakistan Standard Time", - "abbr": "PKT", - "offset": 5, - "isdst": false, - "text": "(UTC+05:00) Islamabad, Karachi", - "utc": [ - "Asia/Karachi" - ] - }, - { - "name": "India Standard Time", - "abbr": "IST", - "offset": 5.5, - "isdst": false, - "text": "(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi", - "utc": [ - "Asia/Kolkata" - ] - }, - { - "name": "Sri Lanka Standard Time", - "abbr": "SLST", - "offset": 5.5, - "isdst": false, - "text": "(UTC+05:30) Sri Jayawardenepura", - "utc": [ - "Asia/Colombo" - ] - }, - { - "name": "Nepal Standard Time", - "abbr": "NST", - "offset": 5.75, - "isdst": false, - "text": "(UTC+05:45) Kathmandu", - "utc": [ - "Asia/Kathmandu" - ] - }, - { - "name": "Central Asia Standard Time", - "abbr": "CAST", - "offset": 6, - "isdst": false, - "text": "(UTC+06:00) Nur-Sultan (Astana)", - "utc": [ - "Antarctica/Vostok", - "Asia/Almaty", - "Asia/Bishkek", - "Asia/Qyzylorda", - "Asia/Urumqi", - "Etc/GMT-6", - "Indian/Chagos" - ] - }, - { - "name": "Bangladesh Standard Time", - "abbr": "BST", - "offset": 6, - "isdst": false, - "text": "(UTC+06:00) Dhaka", - "utc": [ - "Asia/Dhaka", - "Asia/Thimphu" - ] - }, - { - "name": "Myanmar Standard Time", - "abbr": "MST", - "offset": 6.5, - "isdst": false, - "text": "(UTC+06:30) Yangon (Rangoon)", - "utc": [ - "Asia/Rangoon", - "Indian/Cocos" - ] - }, - { - "name": "SE Asia Standard Time", - "abbr": "SAST", - "offset": 7, - "isdst": false, - "text": "(UTC+07:00) Bangkok, Hanoi, Jakarta", - "utc": [ - "Antarctica/Davis", - "Asia/Bangkok", - "Asia/Hovd", - "Asia/Jakarta", - "Asia/Phnom_Penh", - "Asia/Pontianak", - "Asia/Saigon", - "Asia/Vientiane", - "Etc/GMT-7", - "Indian/Christmas" - ] - }, - { - "name": "N. Central Asia Standard Time", - "abbr": "NCAST", - "offset": 7, - "isdst": false, - "text": "(UTC+07:00) Novosibirsk", - "utc": [ - "Asia/Novokuznetsk", - "Asia/Novosibirsk", - "Asia/Omsk" - ] - }, - { - "name": "China Standard Time", - "abbr": "CST", - "offset": 8, - "isdst": false, - "text": "(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi", - "utc": [ - "Asia/Hong_Kong", - "Asia/Macau", - "Asia/Shanghai" - ] - }, - { - "name": "North Asia Standard Time", - "abbr": "NAST", - "offset": 8, - "isdst": false, - "text": "(UTC+08:00) Krasnoyarsk", - "utc": [ - "Asia/Krasnoyarsk" - ] - }, - { - "name": "Singapore Standard Time", - "abbr": "MPST", - "offset": 8, - "isdst": false, - "text": "(UTC+08:00) Kuala Lumpur, Singapore", - "utc": [ - "Asia/Brunei", - "Asia/Kuala_Lumpur", - "Asia/Kuching", - "Asia/Makassar", - "Asia/Manila", - "Asia/Singapore", - "Etc/GMT-8" - ] - }, - { - "name": "W. Australia Standard Time", - "abbr": "WAST", - "offset": 8, - "isdst": false, - "text": "(UTC+08:00) Perth", - "utc": [ - "Antarctica/Casey", - "Australia/Perth" - ] - }, - { - "name": "Taipei Standard Time", - "abbr": "TST", - "offset": 8, - "isdst": false, - "text": "(UTC+08:00) Taipei", - "utc": [ - "Asia/Taipei" - ] - }, - { - "name": "Ulaanbaatar Standard Time", - "abbr": "UST", - "offset": 8, - "isdst": false, - "text": "(UTC+08:00) Ulaanbaatar", - "utc": [ - "Asia/Choibalsan", - "Asia/Ulaanbaatar" - ] - }, - { - "name": "North Asia East Standard Time", - "abbr": "NAEST", - "offset": 8, - "isdst": false, - "text": "(UTC+08:00) Irkutsk", - "utc": [ - "Asia/Irkutsk" - ] - }, - { - "name": "Japan Standard Time", - "abbr": "JST", - "offset": 9, - "isdst": false, - "text": "(UTC+09:00) Osaka, Sapporo, Tokyo", - "utc": [ - "Asia/Dili", - "Asia/Jayapura", - "Asia/Tokyo", - "Etc/GMT-9", - "Pacific/Palau" - ] - }, - { - "name": "Korea Standard Time", - "abbr": "KST", - "offset": 9, - "isdst": false, - "text": "(UTC+09:00) Seoul", - "utc": [ - "Asia/Pyongyang", - "Asia/Seoul" - ] - }, - { - "name": "Cen. Australia Standard Time", - "abbr": "CAST", - "offset": 9.5, - "isdst": false, - "text": "(UTC+09:30) Adelaide", - "utc": [ - "Australia/Adelaide", - "Australia/Broken_Hill" - ] - }, - { - "name": "AUS Central Standard Time", - "abbr": "ACST", - "offset": 9.5, - "isdst": false, - "text": "(UTC+09:30) Darwin", - "utc": [ - "Australia/Darwin" - ] - }, - { - "name": "E. Australia Standard Time", - "abbr": "EAST", - "offset": 10, - "isdst": false, - "text": "(UTC+10:00) Brisbane", - "utc": [ - "Australia/Brisbane", - "Australia/Lindeman" - ] - }, - { - "name": "AUS Eastern Standard Time", - "abbr": "AEST", - "offset": 10, - "isdst": false, - "text": "(UTC+10:00) Canberra, Melbourne, Sydney", - "utc": [ - "Australia/Melbourne", - "Australia/Sydney" - ] - }, - { - "name": "West Pacific Standard Time", - "abbr": "WPST", - "offset": 10, - "isdst": false, - "text": "(UTC+10:00) Guam, Port Moresby", - "utc": [ - "Antarctica/DumontDUrville", - "Etc/GMT-10", - "Pacific/Guam", - "Pacific/Port_Moresby", - "Pacific/Saipan", - "Pacific/Truk" - ] - }, - { - "name": "Tasmania Standard Time", - "abbr": "TST", - "offset": 10, - "isdst": false, - "text": "(UTC+10:00) Hobart", - "utc": [ - "Australia/Currie", - "Australia/Hobart" - ] - }, - { - "name": "Yakutsk Standard Time", - "abbr": "YST", - "offset": 9, - "isdst": false, - "text": "(UTC+09:00) Yakutsk", - "utc": [ - "Asia/Chita", - "Asia/Khandyga", - "Asia/Yakutsk" - ] - }, - { - "name": "Central Pacific Standard Time", - "abbr": "CPST", - "offset": 11, - "isdst": false, - "text": "(UTC+11:00) Solomon Is., New Caledonia", - "utc": [ - "Antarctica/Macquarie", - "Etc/GMT-11", - "Pacific/Efate", - "Pacific/Guadalcanal", - "Pacific/Kosrae", - "Pacific/Noumea", - "Pacific/Ponape" - ] - }, - { - "name": "Vladivostok Standard Time", - "abbr": "VST", - "offset": 11, - "isdst": false, - "text": "(UTC+11:00) Vladivostok", - "utc": [ - "Asia/Sakhalin", - "Asia/Ust-Nera", - "Asia/Vladivostok" - ] - }, - { - "name": "New Zealand Standard Time", - "abbr": "NZST", - "offset": 12, - "isdst": false, - "text": "(UTC+12:00) Auckland, Wellington", - "utc": [ - "Antarctica/McMurdo", - "Pacific/Auckland" - ] - }, - { - "name": "UTC+12", - "abbr": "U", - "offset": 12, - "isdst": false, - "text": "(UTC+12:00) Coordinated Universal Time+12", - "utc": [ - "Etc/GMT-12", - "Pacific/Funafuti", - "Pacific/Kwajalein", - "Pacific/Majuro", - "Pacific/Nauru", - "Pacific/Tarawa", - "Pacific/Wake", - "Pacific/Wallis" - ] - }, - { - "name": "Fiji Standard Time", - "abbr": "FST", - "offset": 12, - "isdst": false, - "text": "(UTC+12:00) Fiji", - "utc": [ - "Pacific/Fiji" - ] - }, - { - "name": "Magadan Standard Time", - "abbr": "MST", - "offset": 12, - "isdst": false, - "text": "(UTC+12:00) Magadan", - "utc": [ - "Asia/Anadyr", - "Asia/Kamchatka", - "Asia/Magadan", - "Asia/Srednekolymsk" - ] - }, - { - "name": "Kamchatka Standard Time", - "abbr": "KDT", - "offset": 13, - "isdst": true, - "text": "(UTC+12:00) Petropavlovsk-Kamchatsky - Old", - "utc": [ - "Asia/Kamchatka" - ] - }, - { - "name": "Tonga Standard Time", - "abbr": "TST", - "offset": 13, - "isdst": false, - "text": "(UTC+13:00) Nuku'alofa", - "utc": [ - "Etc/GMT-13", - "Pacific/Enderbury", - "Pacific/Fakaofo", - "Pacific/Tongatapu" - ] - }, - { - "name": "Samoa Standard Time", - "abbr": "SST", - "offset": 13, - "isdst": false, - "text": "(UTC+13:00) Samoa", - "utc": [ - "Pacific/Apia" - ] - } - ], - //List source: http://answers.google.com/answers/threadview/id/589312.html - profession: [ - "Airline Pilot", - "Academic Team", - "Accountant", - "Account Executive", - "Actor", - "Actuary", - "Acquisition Analyst", - "Administrative Asst.", - "Administrative Analyst", - "Administrator", - "Advertising Director", - "Aerospace Engineer", - "Agent", - "Agricultural Inspector", - "Agricultural Scientist", - "Air Traffic Controller", - "Animal Trainer", - "Anthropologist", - "Appraiser", - "Architect", - "Art Director", - "Artist", - "Astronomer", - "Athletic Coach", - "Auditor", - "Author", - "Baker", - "Banker", - "Bankruptcy Attorney", - "Benefits Manager", - "Biologist", - "Bio-feedback Specialist", - "Biomedical Engineer", - "Biotechnical Researcher", - "Broadcaster", - "Broker", - "Building Manager", - "Building Contractor", - "Building Inspector", - "Business Analyst", - "Business Planner", - "Business Manager", - "Buyer", - "Call Center Manager", - "Career Counselor", - "Cash Manager", - "Ceramic Engineer", - "Chief Executive Officer", - "Chief Operation Officer", - "Chef", - "Chemical Engineer", - "Chemist", - "Child Care Manager", - "Chief Medical Officer", - "Chiropractor", - "Cinematographer", - "City Housing Manager", - "City Manager", - "Civil Engineer", - "Claims Manager", - "Clinical Research Assistant", - "Collections Manager", - "Compliance Manager", - "Comptroller", - "Computer Manager", - "Commercial Artist", - "Communications Affairs Director", - "Communications Director", - "Communications Engineer", - "Compensation Analyst", - "Computer Programmer", - "Computer Ops. Manager", - "Computer Engineer", - "Computer Operator", - "Computer Graphics Specialist", - "Construction Engineer", - "Construction Manager", - "Consultant", - "Consumer Relations Manager", - "Contract Administrator", - "Copyright Attorney", - "Copywriter", - "Corporate Planner", - "Corrections Officer", - "Cosmetologist", - "Credit Analyst", - "Cruise Director", - "Chief Information Officer", - "Chief Technology Officer", - "Customer Service Manager", - "Cryptologist", - "Dancer", - "Data Security Manager", - "Database Manager", - "Day Care Instructor", - "Dentist", - "Designer", - "Design Engineer", - "Desktop Publisher", - "Developer", - "Development Officer", - "Diamond Merchant", - "Dietitian", - "Direct Marketer", - "Director", - "Distribution Manager", - "Diversity Manager", - "Economist", - "EEO Compliance Manager", - "Editor", - "Education Adminator", - "Electrical Engineer", - "Electro Optical Engineer", - "Electronics Engineer", - "Embassy Management", - "Employment Agent", - "Engineer Technician", - "Entrepreneur", - "Environmental Analyst", - "Environmental Attorney", - "Environmental Engineer", - "Environmental Specialist", - "Escrow Officer", - "Estimator", - "Executive Assistant", - "Executive Director", - "Executive Recruiter", - "Facilities Manager", - "Family Counselor", - "Fashion Events Manager", - "Fashion Merchandiser", - "Fast Food Manager", - "Film Producer", - "Film Production Assistant", - "Financial Analyst", - "Financial Planner", - "Financier", - "Fine Artist", - "Wildlife Specialist", - "Fitness Consultant", - "Flight Attendant", - "Flight Engineer", - "Floral Designer", - "Food & Beverage Director", - "Food Service Manager", - "Forestry Technician", - "Franchise Management", - "Franchise Sales", - "Fraud Investigator", - "Freelance Writer", - "Fund Raiser", - "General Manager", - "Geologist", - "General Counsel", - "Geriatric Specialist", - "Gerontologist", - "Glamour Photographer", - "Golf Club Manager", - "Gourmet Chef", - "Graphic Designer", - "Grounds Keeper", - "Hazardous Waste Manager", - "Health Care Manager", - "Health Therapist", - "Health Service Administrator", - "Hearing Officer", - "Home Economist", - "Horticulturist", - "Hospital Administrator", - "Hotel Manager", - "Human Resources Manager", - "Importer", - "Industrial Designer", - "Industrial Engineer", - "Information Director", - "Inside Sales", - "Insurance Adjuster", - "Interior Decorator", - "Internal Controls Director", - "International Acct.", - "International Courier", - "International Lawyer", - "Interpreter", - "Investigator", - "Investment Banker", - "Investment Manager", - "IT Architect", - "IT Project Manager", - "IT Systems Analyst", - "Jeweler", - "Joint Venture Manager", - "Journalist", - "Labor Negotiator", - "Labor Organizer", - "Labor Relations Manager", - "Lab Services Director", - "Lab Technician", - "Land Developer", - "Landscape Architect", - "Law Enforcement Officer", - "Lawyer", - "Lead Software Engineer", - "Lead Software Test Engineer", - "Leasing Manager", - "Legal Secretary", - "Library Manager", - "Litigation Attorney", - "Loan Officer", - "Lobbyist", - "Logistics Manager", - "Maintenance Manager", - "Management Consultant", - "Managed Care Director", - "Managing Partner", - "Manufacturing Director", - "Manpower Planner", - "Marine Biologist", - "Market Res. Analyst", - "Marketing Director", - "Materials Manager", - "Mathematician", - "Membership Chairman", - "Mechanic", - "Mechanical Engineer", - "Media Buyer", - "Medical Investor", - "Medical Secretary", - "Medical Technician", - "Mental Health Counselor", - "Merchandiser", - "Metallurgical Engineering", - "Meteorologist", - "Microbiologist", - "MIS Manager", - "Motion Picture Director", - "Multimedia Director", - "Musician", - "Network Administrator", - "Network Specialist", - "Network Operator", - "New Product Manager", - "Novelist", - "Nuclear Engineer", - "Nuclear Specialist", - "Nutritionist", - "Nursing Administrator", - "Occupational Therapist", - "Oceanographer", - "Office Manager", - "Operations Manager", - "Operations Research Director", - "Optical Technician", - "Optometrist", - "Organizational Development Manager", - "Outplacement Specialist", - "Paralegal", - "Park Ranger", - "Patent Attorney", - "Payroll Specialist", - "Personnel Specialist", - "Petroleum Engineer", - "Pharmacist", - "Photographer", - "Physical Therapist", - "Physician", - "Physician Assistant", - "Physicist", - "Planning Director", - "Podiatrist", - "Political Analyst", - "Political Scientist", - "Politician", - "Portfolio Manager", - "Preschool Management", - "Preschool Teacher", - "Principal", - "Private Banker", - "Private Investigator", - "Probation Officer", - "Process Engineer", - "Producer", - "Product Manager", - "Product Engineer", - "Production Engineer", - "Production Planner", - "Professional Athlete", - "Professional Coach", - "Professor", - "Project Engineer", - "Project Manager", - "Program Manager", - "Property Manager", - "Public Administrator", - "Public Safety Director", - "PR Specialist", - "Publisher", - "Purchasing Agent", - "Publishing Director", - "Quality Assurance Specialist", - "Quality Control Engineer", - "Quality Control Inspector", - "Radiology Manager", - "Railroad Engineer", - "Real Estate Broker", - "Recreational Director", - "Recruiter", - "Redevelopment Specialist", - "Regulatory Affairs Manager", - "Registered Nurse", - "Rehabilitation Counselor", - "Relocation Manager", - "Reporter", - "Research Specialist", - "Restaurant Manager", - "Retail Store Manager", - "Risk Analyst", - "Safety Engineer", - "Sales Engineer", - "Sales Trainer", - "Sales Promotion Manager", - "Sales Representative", - "Sales Manager", - "Service Manager", - "Sanitation Engineer", - "Scientific Programmer", - "Scientific Writer", - "Securities Analyst", - "Security Consultant", - "Security Director", - "Seminar Presenter", - "Ship's Officer", - "Singer", - "Social Director", - "Social Program Planner", - "Social Research", - "Social Scientist", - "Social Worker", - "Sociologist", - "Software Developer", - "Software Engineer", - "Software Test Engineer", - "Soil Scientist", - "Special Events Manager", - "Special Education Teacher", - "Special Projects Director", - "Speech Pathologist", - "Speech Writer", - "Sports Event Manager", - "Statistician", - "Store Manager", - "Strategic Alliance Director", - "Strategic Planning Director", - "Stress Reduction Specialist", - "Stockbroker", - "Surveyor", - "Structural Engineer", - "Superintendent", - "Supply Chain Director", - "System Engineer", - "Systems Analyst", - "Systems Programmer", - "System Administrator", - "Tax Specialist", - "Teacher", - "Technical Support Specialist", - "Technical Illustrator", - "Technical Writer", - "Technology Director", - "Telecom Analyst", - "Telemarketer", - "Theatrical Director", - "Title Examiner", - "Tour Escort", - "Tour Guide Director", - "Traffic Manager", - "Trainer Translator", - "Transportation Manager", - "Travel Agent", - "Treasurer", - "TV Programmer", - "Underwriter", - "Union Representative", - "University Administrator", - "University Dean", - "Urban Planner", - "Veterinarian", - "Vendor Relations Director", - "Viticulturist", - "Warehouse Manager" - ], - animals : { - //list of ocean animals comes from https://owlcation.com/stem/list-of-ocean-animals - "ocean" : ["Acantharea","Anemone","Angelfish King","Ahi Tuna","Albacore","American Oyster","Anchovy","Armored Snail","Arctic Char","Atlantic Bluefin Tuna","Atlantic Cod","Atlantic Goliath Grouper","Atlantic Trumpetfish","Atlantic Wolffish","Baleen Whale","Banded Butterflyfish","Banded Coral Shrimp","Banded Sea Krait","Barnacle","Barndoor Skate","Barracuda","Basking Shark","Bass","Beluga Whale","Bluebanded Goby","Bluehead Wrasse","Bluefish","Bluestreak Cleaner-Wrasse","Blue Marlin","Blue Shark","Blue Spiny Lobster","Blue Tang","Blue Whale","Broadclub Cuttlefish","Bull Shark","Chambered Nautilus","Chilean Basket Star","Chilean Jack Mackerel","Chinook Salmon","Christmas Tree Worm","Clam","Clown Anemonefish","Clown Triggerfish","Cod","Coelacanth","Cockscomb Cup Coral","Common Fangtooth","Conch","Cookiecutter Shark","Copepod","Coral","Corydoras","Cownose Ray","Crab","Crown-of-Thorns Starfish","Cushion Star","Cuttlefish","California Sea Otters","Dolphin","Dolphinfish","Dory","Devil Fish","Dugong","Dumbo Octopus","Dungeness Crab","Eccentric Sand Dollar","Edible Sea Cucumber","Eel","Elephant Seal","Elkhorn Coral","Emperor Shrimp","Estuarine Crocodile","Fathead Sculpin","Fiddler Crab","Fin Whale","Flameback","Flamingo Tongue Snail","Flashlight Fish","Flatback Turtle","Flatfish","Flying Fish","Flounder","Fluke","French Angelfish","Frilled Shark","Fugu (also called Pufferfish)","Gar","Geoduck","Giant Barrel Sponge","Giant Caribbean Sea Anemone","Giant Clam","Giant Isopod","Giant Kingfish","Giant Oarfish","Giant Pacific Octopus","Giant Pyrosome","Giant Sea Star","Giant Squid","Glowing Sucker Octopus","Giant Tube Worm","Goblin Shark","Goosefish","Great White Shark","Greenland Shark","Grey Atlantic Seal","Grouper","Grunion","Guineafowl Puffer","Haddock","Hake","Halibut","Hammerhead Shark","Hapuka","Harbor Porpoise","Harbor Seal","Hatchetfish","Hawaiian Monk Seal","Hawksbill Turtle","Hector's Dolphin","Hermit Crab","Herring","Hoki","Horn Shark","Horseshoe Crab","Humpback Anglerfish","Humpback Whale","Icefish","Imperator Angelfish","Irukandji Jellyfish","Isopod","Ivory Bush Coral","Japanese Spider Crab","Jellyfish","John Dory","Juan Fernandez Fur Seal","Killer Whale","Kiwa Hirsuta","Krill","Lagoon Triggerfish","Lamprey","Leafy Seadragon","Leopard Seal","Limpet","Ling","Lionfish","Lions Mane Jellyfish","Lobe Coral","Lobster","Loggerhead Turtle","Longnose Sawshark","Longsnout Seahorse","Lophelia Coral","Marrus Orthocanna","Manatee","Manta Ray","Marlin","Megamouth Shark","Mexican Lookdown","Mimic Octopus","Moon Jelly","Mollusk","Monkfish","Moray Eel","Mullet","Mussel","Megaladon","Napoleon Wrasse","Nassau Grouper","Narwhal","Nautilus","Needlefish","Northern Seahorse","North Atlantic Right Whale","Northern Red Snapper","Norway Lobster","Nudibranch","Nurse Shark","Oarfish","Ocean Sunfish","Oceanic Whitetip Shark","Octopus","Olive Sea Snake","Orange Roughy","Ostracod","Otter","Oyster","Pacific Angelshark","Pacific Blackdragon","Pacific Halibut","Pacific Sardine","Pacific Sea Nettle Jellyfish","Pacific White Sided Dolphin","Pantropical Spotted Dolphin","Patagonian Toothfish","Peacock Mantis Shrimp","Pelagic Thresher Shark","Penguin","Peruvian Anchoveta","Pilchard","Pink Salmon","Pinniped","Plankton","Porpoise","Polar Bear","Portuguese Man o' War","Pycnogonid Sea Spider","Quahog","Queen Angelfish","Queen Conch","Queen Parrotfish","Queensland Grouper","Ragfish","Ratfish","Rattail Fish","Ray","Red Drum","Red King Crab","Ringed Seal","Risso's Dolphin","Ross Seals","Sablefish","Salmon","Sand Dollar","Sandbar Shark","Sawfish","Sarcastic Fringehead","Scalloped Hammerhead Shark","Seahorse","Sea Cucumber","Sea Lion","Sea Urchin","Seal","Shark","Shortfin Mako Shark","Shovelnose Guitarfish","Shrimp","Silverside Fish","Skipjack Tuna","Slender Snipe Eel","Smalltooth Sawfish","Smelts","Sockeye Salmon","Southern Stingray","Sponge","Spotted Porcupinefish","Spotted Dolphin","Spotted Eagle Ray","Spotted Moray","Squid","Squidworm","Starfish","Stickleback","Stonefish","Stoplight Loosejaw","Sturgeon","Swordfish","Tan Bristlemouth","Tasseled Wobbegong","Terrible Claw Lobster","Threespot Damselfish","Tiger Prawn","Tiger Shark","Tilefish","Toadfish","Tropical Two-Wing Flyfish","Tuna","Umbrella Squid","Velvet Crab","Venus Flytrap Sea Anemone","Vigtorniella Worm","Viperfish","Vampire Squid","Vaquita","Wahoo","Walrus","West Indian Manatee","Whale","Whale Shark","Whiptail Gulper","White-Beaked Dolphin","White-Ring Garden Eel","White Shrimp","Wobbegong","Wrasse","Wreckfish","Xiphosura","Yellowtail Damselfish","Yelloweye Rockfish","Yellow Cup Black Coral","Yellow Tube Sponge","Yellowfin Tuna","Zebrashark","Zooplankton"], - //list of desert, grassland, and forest animals comes from http://www.skyenimals.com/ - "desert" : ["Aardwolf","Addax","African Wild Ass","Ant","Antelope","Armadillo","Baboon","Badger","Bat","Bearded Dragon","Beetle","Bird","Black-footed Cat","Boa","Brown Bear","Bustard","Butterfly","Camel","Caracal","Caracara","Caterpillar","Centipede","Cheetah","Chipmunk","Chuckwalla","Climbing Mouse","Coati","Cobra","Cotton Rat","Cougar","Courser","Crane Fly","Crow","Dassie Rat","Dove","Dunnart","Eagle","Echidna","Elephant","Emu","Falcon","Fly","Fox","Frogmouth","Gecko","Geoffroy's Cat","Gerbil","Grasshopper","Guanaco","Gundi","Hamster","Hawk","Hedgehog","Hyena","Hyrax","Jackal","Kangaroo","Kangaroo Rat","Kestrel","Kowari","Kultarr","Leopard","Lion","Macaw","Meerkat","Mouse","Oryx","Ostrich","Owl","Pronghorn","Python","Rabbit","Raccoon","Rattlesnake","Rhinoceros","Sand Cat","Spectacled Bear","Spiny Mouse","Starling","Stick Bug","Tarantula","Tit","Toad","Tortoise","Tyrant Flycatcher","Viper","Vulture","Waxwing","Xerus","Zebra"], - "grassland" : ["Aardvark","Aardwolf","Accentor","African Buffalo","African Wild Dog","Alpaca","Anaconda","Ant","Anteater","Antelope","Armadillo","Baboon","Badger","Bandicoot","Barbet","Bat","Bee","Bee-eater","Beetle","Bird","Bison","Black-footed Cat","Black-footed Ferret","Bluebird","Boa","Bowerbird","Brown Bear","Bush Dog","Bushshrike","Bustard","Butterfly","Buzzard","Caracal","Caracara","Cardinal","Caterpillar","Cheetah","Chipmunk","Civet","Climbing Mouse","Clouded Leopard","Coati","Cobra","Cockatoo","Cockroach","Common Genet","Cotton Rat","Cougar","Courser","Coyote","Crane","Crane Fly","Cricket","Crow","Culpeo","Death Adder","Deer","Deer Mouse","Dingo","Dinosaur","Dove","Drongo","Duck","Duiker","Dunnart","Eagle","Echidna","Elephant","Elk","Emu","Falcon","Finch","Flea","Fly","Flying Frog","Fox","Frog","Frogmouth","Garter Snake","Gazelle","Gecko","Geoffroy's Cat","Gerbil","Giant Tortoise","Giraffe","Grasshopper","Grison","Groundhog","Grouse","Guanaco","Guinea Pig","Hamster","Harrier","Hartebeest","Hawk","Hedgehog","Helmetshrike","Hippopotamus","Hornbill","Hyena","Hyrax","Impala","Jackal","Jaguar","Jaguarundi","Kangaroo","Kangaroo Rat","Kestrel","Kultarr","Ladybug","Leopard","Lion","Macaw","Meerkat","Mouse","Newt","Oryx","Ostrich","Owl","Pangolin","Pheasant","Prairie Dog","Pronghorn","Przewalski's Horse","Python","Quoll","Rabbit","Raven","Rhinoceros","Shelduck","Sloth Bear","Spectacled Bear","Squirrel","Starling","Stick Bug","Tamandua","Tasmanian Devil","Thornbill","Thrush","Toad","Tortoise"], - "forest" : ["Agouti","Anaconda","Anoa","Ant","Anteater","Antelope","Armadillo","Asian Black Bear","Aye-aye","Babirusa","Baboon","Badger","Bandicoot","Banteng","Barbet","Basilisk","Bat","Bearded Dragon","Bee","Bee-eater","Beetle","Bettong","Binturong","Bird-of-paradise","Bongo","Bowerbird","Bulbul","Bush Dog","Bushbaby","Bushshrike","Butterfly","Buzzard","Caecilian","Cardinal","Cassowary","Caterpillar","Centipede","Chameleon","Chimpanzee","Cicada","Civet","Clouded Leopard","Coati","Cobra","Cockatoo","Cockroach","Colugo","Cotinga","Cotton Rat","Cougar","Crane Fly","Cricket","Crocodile","Crow","Cuckoo","Cuscus","Death Adder","Deer","Dhole","Dingo","Dinosaur","Drongo","Duck","Duiker","Eagle","Echidna","Elephant","Finch","Flat-headed Cat","Flea","Flowerpecker","Fly","Flying Frog","Fossa","Frog","Frogmouth","Gaur","Gecko","Gorilla","Grison","Hawaiian Honeycreeper","Hawk","Hedgehog","Helmetshrike","Hornbill","Hyrax","Iguana","Jackal","Jaguar","Jaguarundi","Kestrel","Ladybug","Lemur","Leopard","Lion","Macaw","Mandrill","Margay","Monkey","Mouse","Mouse Deer","Newt","Okapi","Old World Flycatcher","Orangutan","Owl","Pangolin","Peafowl","Pheasant","Possum","Python","Quokka","Rabbit","Raccoon","Red Panda","Red River Hog","Rhinoceros","Sloth Bear","Spectacled Bear","Squirrel","Starling","Stick Bug","Sun Bear","Tamandua","Tamarin","Tapir","Tarantula","Thrush","Tiger","Tit","Toad","Tortoise","Toucan","Trogon","Trumpeter","Turaco","Turtle","Tyrant Flycatcher","Viper","Vulture","Wallaby","Warbler","Wasp","Waxwing","Weaver","Weaver-finch","Whistler","White-eye","Whydah","Woodswallow","Worm","Wren","Xenops","Yellowjacket","Accentor","African Buffalo","American Black Bear","Anole","Bird","Bison","Boa","Brown Bear","Chipmunk","Common Genet","Copperhead","Coyote","Deer Mouse","Dormouse","Elk","Emu","Fisher","Fox","Garter Snake","Giant Panda","Giant Tortoise","Groundhog","Grouse","Guanaco","Himalayan Tahr","Kangaroo","Koala","Numbat","Quoll","Raccoon dog","Tasmanian Devil","Thornbill","Turkey","Vole","Weasel","Wildcat","Wolf","Wombat","Woodchuck","Woodpecker"], - //list of farm animals comes from https://www.buzzle.com/articles/farm-animals-list.html - "farm" : ["Alpaca","Buffalo","Banteng","Cow","Cat","Chicken","Carp","Camel","Donkey","Dog","Duck","Emu","Goat","Gayal","Guinea","Goose","Horse","Honey","Llama","Pig","Pigeon","Rhea","Rabbit","Sheep","Silkworm","Turkey","Yak","Zebu"], - //list of pet animals comes from https://www.dogbreedinfo.com/pets/pet.htm - "pet" : ["Bearded Dragon","Birds","Burro","Cats","Chameleons","Chickens","Chinchillas","Chinese Water Dragon","Cows","Dogs","Donkey","Ducks","Ferrets","Fish","Geckos","Geese","Gerbils","Goats","Guinea Fowl","Guinea Pigs","Hamsters","Hedgehogs","Horses","Iguanas","Llamas","Lizards","Mice","Mule","Peafowl","Pigs and Hogs","Pigeons","Ponies","Pot Bellied Pig","Rabbits","Rats","Sheep","Skinks","Snakes","Stick Insects","Sugar Gliders","Tarantula","Turkeys","Turtles"], - //list of zoo animals comes from https://bronxzoo.com/animals - "zoo" : ["Aardvark","African Wild Dog","Aldabra Tortoise","American Alligator","American Bison","Amur Tiger","Anaconda","Andean Condor","Asian Elephant","Baby Doll Sheep","Bald Eagle","Barred Owl","Blue Iguana","Boer Goat","California Sea Lion","Caribbean Flamingo","Chinchilla","Collared Lemur","Coquerel's Sifaka","Cuban Amazon Parrot","Ebony Langur","Fennec Fox","Fossa","Gelada","Giant Anteater","Giraffe","Gorilla","Grizzly Bear","Henkel's Leaf-tailed Gecko","Indian Gharial","Indian Rhinoceros","King Cobra","King Vulture","Komodo Dragon","Linne's Two-toed Sloth","Lion","Little Penguin","Madagascar Tree Boa","Magellanic Penguin","Malayan Tapir","Malayan Tiger","Matschies Tree Kangaroo","Mini Donkey","Monarch Butterfly","Nile crocodile","North American Porcupine","Nubian Ibex","Okapi","Poison Dart Frog","Polar Bear","Pygmy Marmoset","Radiated Tortoise","Red Panda","Red Ruffed Lemur","Ring-tailed Lemur","Ring-tailed Mongoose","Rock Hyrax","Small Clawed Asian Otter","Snow Leopard","Snowy Owl","Southern White-faced Owl","Southern White Rhinocerous","Squirrel Monkey","Tufted Puffin","White Cheeked Gibbon","White-throated Bee Eater","Zebra"] - }, - primes: [ - // 1230 first primes, i.e. all primes up to the first one greater than 10000, inclusive. - 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,1171,1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,1277,1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367,1373,1381,1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,1487,1489,1493,1499,1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,1597,1601,1607,1609,1613,1619,1621,1627,1637,1657,1663,1667,1669,1693,1697,1699,1709,1721,1723,1733,1741,1747,1753,1759,1777,1783,1787,1789,1801,1811,1823,1831,1847,1861,1867,1871,1873,1877,1879,1889,1901,1907,1913,1931,1933,1949,1951,1973,1979,1987,1993,1997,1999,2003,2011,2017,2027,2029,2039,2053,2063,2069,2081,2083,2087,2089,2099,2111,2113,2129,2131,2137,2141,2143,2153,2161,2179,2203,2207,2213,2221,2237,2239,2243,2251,2267,2269,2273,2281,2287,2293,2297,2309,2311,2333,2339,2341,2347,2351,2357,2371,2377,2381,2383,2389,2393,2399,2411,2417,2423,2437,2441,2447,2459,2467,2473,2477,2503,2521,2531,2539,2543,2549,2551,2557,2579,2591,2593,2609,2617,2621,2633,2647,2657,2659,2663,2671,2677,2683,2687,2689,2693,2699,2707,2711,2713,2719,2729,2731,2741,2749,2753,2767,2777,2789,2791,2797,2801,2803,2819,2833,2837,2843,2851,2857,2861,2879,2887,2897,2903,2909,2917,2927,2939,2953,2957,2963,2969,2971,2999,3001,3011,3019,3023,3037,3041,3049,3061,3067,3079,3083,3089,3109,3119,3121,3137,3163,3167,3169,3181,3187,3191,3203,3209,3217,3221,3229,3251,3253,3257,3259,3271,3299,3301,3307,3313,3319,3323,3329,3331,3343,3347,3359,3361,3371,3373,3389,3391,3407,3413,3433,3449,3457,3461,3463,3467,3469,3491,3499,3511,3517,3527,3529,3533,3539,3541,3547,3557,3559,3571,3581,3583,3593,3607,3613,3617,3623,3631,3637,3643,3659,3671,3673,3677,3691,3697,3701,3709,3719,3727,3733,3739,3761,3767,3769,3779,3793,3797,3803,3821,3823,3833,3847,3851,3853,3863,3877,3881,3889,3907,3911,3917,3919,3923,3929,3931,3943,3947,3967,3989,4001,4003,4007,4013,4019,4021,4027,4049,4051,4057,4073,4079,4091,4093,4099,4111,4127,4129,4133,4139,4153,4157,4159,4177,4201,4211,4217,4219,4229,4231,4241,4243,4253,4259,4261,4271,4273,4283,4289,4297,4327,4337,4339,4349,4357,4363,4373,4391,4397,4409,4421,4423,4441,4447,4451,4457,4463,4481,4483,4493,4507,4513,4517,4519,4523,4547,4549,4561,4567,4583,4591,4597,4603,4621,4637,4639,4643,4649,4651,4657,4663,4673,4679,4691,4703,4721,4723,4729,4733,4751,4759,4783,4787,4789,4793,4799,4801,4813,4817,4831,4861,4871,4877,4889,4903,4909,4919,4931,4933,4937,4943,4951,4957,4967,4969,4973,4987,4993,4999,5003,5009,5011,5021,5023,5039,5051,5059,5077,5081,5087,5099,5101,5107,5113,5119,5147,5153,5167,5171,5179,5189,5197,5209,5227,5231,5233,5237,5261,5273,5279,5281,5297,5303,5309,5323,5333,5347,5351,5381,5387,5393,5399,5407,5413,5417,5419,5431,5437,5441,5443,5449,5471,5477,5479,5483,5501,5503,5507,5519,5521,5527,5531,5557,5563,5569,5573,5581,5591,5623,5639,5641,5647,5651,5653,5657,5659,5669,5683,5689,5693,5701,5711,5717,5737,5741,5743,5749,5779,5783,5791,5801,5807,5813,5821,5827,5839,5843,5849,5851,5857,5861,5867,5869,5879,5881,5897,5903,5923,5927,5939,5953,5981,5987,6007,6011,6029,6037,6043,6047,6053,6067,6073,6079,6089,6091,6101,6113,6121,6131,6133,6143,6151,6163,6173,6197,6199,6203,6211,6217,6221,6229,6247,6257,6263,6269,6271,6277,6287,6299,6301,6311,6317,6323,6329,6337,6343,6353,6359,6361,6367,6373,6379,6389,6397,6421,6427,6449,6451,6469,6473,6481,6491,6521,6529,6547,6551,6553,6563,6569,6571,6577,6581,6599,6607,6619,6637,6653,6659,6661,6673,6679,6689,6691,6701,6703,6709,6719,6733,6737,6761,6763,6779,6781,6791,6793,6803,6823,6827,6829,6833,6841,6857,6863,6869,6871,6883,6899,6907,6911,6917,6947,6949,6959,6961,6967,6971,6977,6983,6991,6997,7001,7013,7019,7027,7039,7043,7057,7069,7079,7103,7109,7121,7127,7129,7151,7159,7177,7187,7193,7207,7211,7213,7219,7229,7237,7243,7247,7253,7283,7297,7307,7309,7321,7331,7333,7349,7351,7369,7393,7411,7417,7433,7451,7457,7459,7477,7481,7487,7489,7499,7507,7517,7523,7529,7537,7541,7547,7549,7559,7561,7573,7577,7583,7589,7591,7603,7607,7621,7639,7643,7649,7669,7673,7681,7687,7691,7699,7703,7717,7723,7727,7741,7753,7757,7759,7789,7793,7817,7823,7829,7841,7853,7867,7873,7877,7879,7883,7901,7907,7919,7927,7933,7937,7949,7951,7963,7993,8009,8011,8017,8039,8053,8059,8069,8081,8087,8089,8093,8101,8111,8117,8123,8147,8161,8167,8171,8179,8191,8209,8219,8221,8231,8233,8237,8243,8263,8269,8273,8287,8291,8293,8297,8311,8317,8329,8353,8363,8369,8377,8387,8389,8419,8423,8429,8431,8443,8447,8461,8467,8501,8513,8521,8527,8537,8539,8543,8563,8573,8581,8597,8599,8609,8623,8627,8629,8641,8647,8663,8669,8677,8681,8689,8693,8699,8707,8713,8719,8731,8737,8741,8747,8753,8761,8779,8783,8803,8807,8819,8821,8831,8837,8839,8849,8861,8863,8867,8887,8893,8923,8929,8933,8941,8951,8963,8969,8971,8999,9001,9007,9011,9013,9029,9041,9043,9049,9059,9067,9091,9103,9109,9127,9133,9137,9151,9157,9161,9173,9181,9187,9199,9203,9209,9221,9227,9239,9241,9257,9277,9281,9283,9293,9311,9319,9323,9337,9341,9343,9349,9371,9377,9391,9397,9403,9413,9419,9421,9431,9433,9437,9439,9461,9463,9467,9473,9479,9491,9497,9511,9521,9533,9539,9547,9551,9587,9601,9613,9619,9623,9629,9631,9643,9649,9661,9677,9679,9689,9697,9719,9721,9733,9739,9743,9749,9767,9769,9781,9787,9791,9803,9811,9817,9829,9833,9839,9851,9857,9859,9871,9883,9887,9901,9907,9923,9929,9931,9941,9949,9967,9973,10007 - ], - emotions: [ - "love", - "joy", - "surprise", - "anger", - "sadness", - "fear" - ], - music_genres: { - 'general': [ - 'Rock', - 'Pop', - 'Hip-Hop', - 'Jazz', - 'Classical', - 'Electronic', - 'Country', - 'R&B', - 'Reggae', - 'Blues', - 'Metal', - 'Folk', - 'Alternative', - 'Punk', - 'Disco', - 'Funk', - 'Techno', - 'Indie', - 'Gospel', - 'Dance', - 'Children\'s', - 'World' - ], - 'alternative': [ - 'Art Punk', - 'Alternative Rock', - 'Britpunk', - 'College Rock', - 'Crossover Thrash', - 'Crust Punk', - 'Emo / Emocore', - 'Experimental Rock', - 'Folk Punk', - 'Goth / Gothic Rock', - 'Grunge', - 'Hardcore Punk', - 'Hard Rock', - 'Indie Rock', - 'Lo-fi', - 'Musique Concrète', - 'New Wave', - 'Progressive Rock', - 'Punk', - 'Shoegaze', - 'Steampunk', - ], 'blues': [ - 'Acoustic Blues', - 'African Blues', - 'Blues Rock', - 'Blues Shouter', - 'British Blues', - 'Canadian Blues', - 'Chicago Blues', - 'Classic Blues', - 'Classic Female Blues', - 'Contemporary Blues', - 'Country Blues', - 'Dark Blues', - 'Delta Blues', - 'Detroit Blues', - 'Doom Blues', - 'Electric Blues', - 'Folk Blues', - 'Gospel Blues', - 'Harmonica Blues', - 'Hill Country Blues', - 'Hokum Blues', - 'Jazz Blues', - 'Jump Blues', - 'Kansas City Blues', - 'Louisiana Blues', - 'Memphis Blues', - 'Modern Blues', - 'New Orlean Blues', - 'NY Blues', - 'Piano Blues', - 'Piedmont Blues', - 'Punk Blues', - 'Ragtime Blues', - 'Rhythm Blues', - 'Soul Blues', - 'St.Louis Blues', - 'Soul Blues', - 'Swamp Blues', - 'Texas Blues', - 'Urban Blues', - 'Vandeville', - 'West Coast Blues', - ], 'children\'s': [ - 'Lullabies', - 'Sing - Along', - 'Stories' - ], 'classical': [ - 'Avant-Garde', - 'Ballet', - 'Baroque', - 'Cantata', - 'Chamber Music', - 'String Quartet', - 'Chant', - 'Choral', - 'Classical Crossover', - 'Concerto', - 'Concerto Grosso', - 'Contemporary Classical', - 'Early Music', - 'Expressionist', - 'High Classical', - 'Impressionist', - 'Mass Requiem', - 'Medieval', - 'Minimalism', - 'Modern Composition', - 'Modern Classical', - 'Opera', - 'Oratorio', - 'Orchestral', - 'Organum', - 'Renaissance', - 'Romantic (early period)', - 'Romantic (later period)', - 'Sonata', - 'Symphonic', - 'Symphony', - 'Twelve-tone', - 'Wedding Music' - ], 'country': [ - 'Alternative Country', - 'Americana', - 'Australian Country', - 'Bakersfield Sound', - 'Bluegrass', - 'Blues Country', - 'Cajun Fiddle Tunes', - 'Christian Country', - 'Classic Country', - 'Close Harmony', - 'Contemporary Bluegrass', - 'Contemporary Country', - 'Country Gospel', - 'Country Pop', - 'Country Rap', - 'Country Rock', - 'Country Soul', - 'Cowboy / Western', - 'Cowpunk', - 'Dansband', - 'Honky Tonk', - 'Franco-Country', - 'Gulf and Western', - 'Hellbilly Music', - 'Honky Tonk', - 'Instrumental Country', - 'Lubbock Sound', - 'Nashville Sound', - 'Neotraditional Country', - 'Outlaw Country', - 'Progressive', - 'Psychobilly / Punkabilly', - 'Red Dirt', - 'Sertanejo', - 'Texas County', - 'Traditional Bluegrass', - 'Traditional Country', - 'Truck-Driving Country', - 'Urban Cowboy', - 'Western Swing' - ], 'dance': [ - 'Club / Club Dance', - 'Breakcore', - 'Breakbeat / Breakstep', - 'Chillstep', - 'Deep House', - 'Dubstep', - 'Dancehall', - 'Electro House', - 'Electroswing', - 'Exercise', - 'Future Garage', - 'Garage', - 'Glitch Hop', - 'Glitch Pop', - 'Grime', - 'Hardcore', - 'Hard Dance', - 'Hi-NRG / Eurodance', - 'Horrorcore', - 'House', - 'Jackin House', - 'Jungle / Drum n bass', - 'Liquid Dub', - 'Regstep', - 'Speedcore', - 'Techno', - 'Trance', - 'Trap' - ], electronic: [ - '2-Step', - '8bit', - 'Ambient', - 'Asian Underground', - 'Bassline', - 'Chillwave', - 'Chiptune', - 'Crunk', - 'Downtempo', - 'Drum & Bass', - 'Hard Step', - 'Electro', - 'Electro-swing', - 'Electroacoustic', - 'Electronica', - 'Electronic Rock', - 'Eurodance', - 'Hardstyle', - 'Hi-Nrg', - 'IDM/Experimental', - 'Industrial', - 'Trip Hop', - 'Vaporwave', - 'UK Garage', - 'House', - 'Dubstep', - 'Deep House', - 'EDM', - 'Future Bass', - 'Psychedelic trance' - ], 'jazz' : [ - 'Acid Jazz', - 'Afro-Cuban Jazz', - 'Avant-Garde Jazz', - 'Bebop', - 'Big Band', - 'Blue Note', - 'British Dance Band (Jazz)', - 'Cape Jazz', - 'Chamber Jazz', - 'Contemporary Jazz', - 'Continental Jazz', - 'Cool Jazz', - 'Crossover Jazz', - 'Dark Jazz', - 'Dixieland', - 'Early Jazz', - 'Electro Swing (Jazz)', - 'Ethio-jazz', - 'Ethno-Jazz', - 'European Free Jazz', - 'Free Funk (Avant-Garde / Funk Jazz)', - 'Free Jazz', - 'Fusion', - 'Gypsy Jazz', - 'Hard Bop', - 'Indo Jazz', - 'Jazz Blues', - 'Jazz-Funk (see Free Funk)', - 'Jazz-Fusion', - 'Jazz Rap', - 'Jazz Rock', - 'Kansas City Jazz', - 'Latin Jazz', - 'M-Base Jazz', - 'Mainstream Jazz', - 'Modal Jazz', - 'Neo-Bop', - 'Neo-Swing', - 'Nu Jazz', - 'Orchestral Jazz', - 'Post-Bop', - 'Punk Jazz', - 'Ragtime', - 'Ska Jazz', - 'Skiffle (also Folk)', - 'Smooth Jazz', - 'Soul Jazz', - 'Swing Jazz', - 'Straight-Ahead Jazz', - 'Trad Jazz', - 'Third Stream', - 'Jazz-Funk', - 'Free Jazz', - 'West Coast Jazz' - ], 'metal': [ - 'Heavy Metal', - 'Speed Metal', - 'Thrash Metal', - 'Power Metal', - 'Death Metal', - 'Black Metal', - 'Pagan Metal', - 'Viking Metal', - 'Folk Metal', - 'Symphonic Metal', - 'Gothic Metal', - 'Glam Metal', - 'Hair Metal', - 'Doom Metal', - 'Groove Metal', - 'Industrial Metal', - 'Modern Metal', - 'Neoclassical Metal', - 'New Wave Of British Heavy Metal', - 'Post Metal', - 'Progressive Metal', - 'Avantgarde Metal', - 'Sludge', - 'Djent', - 'Drone', - 'Kawaii Metal', - 'Pirate Metal', - 'Nu Metal', - 'Neue Deutsche Härte', - 'Math Metal', - 'Crossover', - 'Grindcore', - 'Hardcore', - 'Metalcore', - 'Deathcore', - 'Post Hardcore', - 'Mathcore' - ], 'folk': [ - 'American Folk Revival', - 'Anti - Folk', - 'British Folk Revival', - 'Contemporary Folk', - 'Filk Music', - 'Freak Folk', - 'Indie Folk', - 'Industrial Folk', - 'Neofolk', - 'Progressive Folk', - 'Psychedelic Folk', - 'Sung Poetry', - 'Techno - Folk', - 'Folk Rock', - 'Old-time Music', - 'Bluegrass', - 'Appalachian', - 'Roots Revival', - 'Celtic', - 'Indie Folk' - ], 'pop': [ - 'Adult Contemporary', - 'Arab Pop', - 'Baroque', - 'Britpop', - 'Bubblegum Pop', - 'Chamber Pop', - 'Chanson', - 'Christian Pop', - 'Classical Crossover', - 'Europop', - 'Austropop', - 'Balkan Pop', - 'French Pop', - 'Korean Pop', - 'Japanese Pop', - 'Chinese Pop', - 'Latin Pop', - 'Laïkó', - 'Nederpop', - 'Russian Pop', - 'Dance Pop', - 'Dream Pop', - 'Electro Pop', - 'Iranian Pop', - 'Jangle Pop', - 'Latin Ballad', - 'Levenslied', - 'Louisiana Swamp Pop', - 'Mexican Pop', - 'Motorpop', - 'New Romanticism', - 'Orchestral Pop', - 'Pop Rap', - 'Popera', - 'Pop / Rock', - 'Pop Punk', - 'Power Pop', - 'Psychedelic Pop', - 'Russian Pop', - 'Schlager', - 'Soft Rock', - 'Sophisti - Pop', - 'Space Age Pop', - 'Sunshine Pop', - 'Surf Pop', - 'Synthpop', - 'Teen Pop', - 'Traditional Pop Music', - 'Turkish Pop', - 'Vispop', - 'Wonky Pop' - ], 'r&b': [ - '(Carolina) Beach Music', - 'Contemporary R & B', - 'Disco', - 'Doo Wop', - 'Funk', - 'Modern Soul', - 'Motown', - 'Neo - Soul', - 'Northern Soul', - 'Psychedelic Soul', - 'Quiet Storm', - 'Soul', - 'Soul Blues', - 'Southern Soul' - ], 'reggae': [ - '2 - Tone', - 'Dub', - 'Roots Reggae', - 'Reggae Fusion', - 'Reggae en Español', - 'Spanish Reggae', - 'Reggae 110', - 'Reggae Bultrón', - 'Romantic Flow', - 'Lovers Rock', - 'Raggamuffin', - 'Ragga', - 'Dancehall', - 'Ska', - ], 'rock': [ - 'Acid Rock', - 'Adult - Oriented Rock', - 'Afro Punk', - 'Adult Alternative', - 'Alternative Rock', - 'American Traditional Rock', - 'Anatolian Rock', - 'Arena Rock', - 'Art Rock', - 'Blues - Rock', - 'British Invasion', - 'Cock Rock', - 'Death Metal / Black Metal', - 'Doom Metal', - 'Glam Rock', - 'Gothic Metal', - 'Grind Core', - 'Hair Metal', - 'Hard Rock', - 'Math Metal', - 'Math Rock', - 'Metal', - 'Metal Core', - 'Noise Rock', - 'Jam Bands', - 'Post Punk', - 'Post Rock', - 'Prog - Rock / Art Rock', - 'Progressive Metal', - 'Psychedelic', - 'Rock & Roll', - 'Rockabilly', - 'Roots Rock', - 'Singer / Songwriter', - 'Southern Rock', - 'Spazzcore', - 'Stoner Metal', - 'Surf', - 'Technical Death Metal', - 'Tex - Mex', - 'Thrash Metal', - 'Time Lord Rock(Trock)', - 'Trip - hop', - 'Yacht Rock', - 'School House Rock' - ], 'hip-hop': [ - 'Alternative Rap', - 'Avant - Garde', - 'Bounce', - 'Chap Hop', - 'Christian Hip Hop', - 'Conscious Hip Hop', - 'Country - Rap', - 'Grunk', - 'Crunkcore', - 'Cumbia Rap', - 'Dirty South', - 'East Coast', - 'Brick City Club', - 'Hardcore Hip Hop', - 'Mafioso Rap', - 'New Jersey Hip Hop', - 'Freestyle Rap', - 'G - Funk', - 'Gangsta Rap', - 'Golden Age', - 'Grime', - 'Hardcore Rap', - 'Hip - Hop', - 'Hip Pop', - 'Horrorcore', - 'Hyphy', - 'Industrial Hip Hop', - 'Instrumental Hip Hop', - 'Jazz Rap', - 'Latin Rap', - 'Low Bap', - 'Lyrical Hip Hop', - 'Merenrap', - 'Midwest Hip Hop', - 'Chicago Hip Hop', - 'Detroit Hip Hop', - 'Horrorcore', - 'St.Louis Hip Hop', - 'Twin Cities Hip Hop', - 'Motswako', - 'Nerdcore', - 'New Jack Swing', - 'New School Hip Hop', - 'Old School Rap', - 'Rap', - 'Trap', - 'Turntablism', - 'Underground Rap', - 'West Coast Rap', - 'East Coast Rap', - 'Trap', - 'UK Grime', - 'Hyphy', - 'Emo-rap', - 'Cloud rap', - 'G-funk', - 'Boom Bap', - 'Mumble', - 'Drill', - 'UK Drill', - 'Soundcloud Rap', - 'Lo-fi' - ], 'punk': [ - 'Afro-punk', - 'Anarcho punk', - 'Art punk', - 'Christian punk', - 'Crust punk', - 'Deathrock', - 'Egg punk', - 'Garage punk', - 'Glam punk', - 'Hardcore punk', - 'Horror punk', - 'Incelcore/e-punk', - 'Oi!', - 'Peace punk', - 'Punk pathetique', - 'Queercore', - 'Riot Grrrl', - 'Skate punk', - 'Street punk', - 'Taqwacore', - 'Trallpunk' - ], 'disco': [ - 'Nu-disco', - 'Disco-funk', - 'Hi-NRG', - 'Italo Disco', - 'Eurodisco', - 'Boogie', - 'Space Disco', - 'Post-disco', - 'Electro Disco', - 'Disco House', - 'Disco Pop', - 'Soulful House' - ], 'funk': [ - 'Funk Rock', - 'P-Funk (Parliament-Funkadelic)', - 'Psychedelic Funk', - 'Funk Metal', - 'Electro-Funk', - 'Go-go', - 'Boogie-Funk', - 'Jazz-Funk', - 'Soul-Funk', - 'Funky Disco', - 'Nu-Funk', - 'Afrobeat', - 'Latin Funk', - 'G-Funk', - 'Acid Jazz', - 'Funktronica', - 'Folk-Funk', - 'Space Funk', - 'Ambient Funk', - 'Hard Funk', - 'Fusion Funk' - ], 'techno': [ - 'Acid Techno', - 'Ambient Techno', - 'Detroit Techno', - 'Dub Techno', - 'Minimal Techno', - 'Industrial Techno', - 'Hard Techno', - 'Trance', - 'Progressive Techno', - 'Tech House', - 'Electronica', - 'Breakbeat Techno', - 'Electro Techno', - 'Melodic Techno', - 'Experimental Techno', - 'Dark Techno', - 'Ebm', - 'Hypnotic Techno', - 'Psychedelic Techno', - 'Rave Techno', - 'Techno-Pop' - ], 'indie': [ - 'Indie Rock', - 'Indie Pop', - 'Indie Folk', - 'Indie Electronic', - 'Indie Punk', - 'Indie Hip-Hop', - 'Dream Pop', - 'Shoegaze', - 'Lo-fi', - 'Chillwave', - 'Freak Folk', - 'Noise Pop', - 'Math Rock', - 'Post-Punk', - 'Garage Rock', - 'Experimental Indie', - 'Surf Rock', - 'Alternative Country', - 'Indie Soul', - 'Art Rock', - 'Indie R&B', - 'Indietronica', - 'Emo', - 'Post-Rock', - 'Indie Pop-Rock', - 'Indie Synthpop', - 'Noise Rock', - 'Psych Folk', - 'Indie Blues' - ], 'gospel': [ - 'Traditional Gospel', - 'Contemporary Gospel', - 'Southern Gospel', - 'Black Gospel', - 'Urban Contemporary Gospel', - 'Gospel Blues', - 'Bluegrass Gospel', - 'Country Gospel', - 'Praise and Worship', - 'Christian Hip-Hop', - 'Gospel Jazz', - 'Reggae Gospel', - 'African Gospel', - 'Latin Gospel', - 'R&B Gospel', - 'Gospel Choir', - 'Acappella Gospel', - 'Instrumental Gospel', - 'Gospel Rap' - ], 'world': [ - 'African', - 'Arabic', - 'Asian', - 'Caribbean', - 'Celtic', - 'European', - 'Latin American', - 'Middle Eastern', - 'Native American', - 'Polynesian', - 'Reggae', - 'Ska', - 'Salsa', - 'Flamenco', - 'Bossa Nova', - 'Tango', - 'Fado', - 'Klezmer', - 'Balkan', - 'Afrobeat', - 'Mongolian Throat Singing', - 'Indian Classical', - 'Gamelan', - 'Sufi Music', - 'Zydeco', - 'Kora Music', - 'Andean Music', - 'Irish Traditional', - 'Gypsy Jazz', - 'Bollywood', - 'Bhangra', - 'Jawaiian', - 'Hawaiian Slack Key Guitar', - 'Calypso', - 'Cuban Son', - 'Taiko Drumming', - 'African Highlife', - 'Merengue', - 'Tuvan Throat Singing' - ] - }, - - // Data sourced from https://unicode.org/emoji/charts/full-emoji-list.html - emojis: { - "smileys_and_emotion": [ - "0x1f600", - "0x1f603", - "0x1f604", - "0x1f601", - "0x1f606", - "0x1f605", - "0x1f923", - "0x1f602", - "0x1f642", - "0x1f643", - "0x1fae0", - "0x1f609", - "0x1f60a", - "0x1f607", - "0x1f970", - "0x1f60d", - "0x1f929", - "0x1f618", - "0x1f617", - "0x263a", - "0x1f61a", - "0x1f619", - "0x1f972", - "0x1f60b", - "0x1f61b", - "0x1f61c", - "0x1f92a", - "0x1f61d", - "0x1f911", - "0x1f917", - "0x1f92d", - "0x1fae2", - "0x1fae3", - "0x1f92b", - "0x1f914", - "0x1fae1", - "0x1f910", - "0x1f928", - "0x1f610", - "0x1f611", - "0x1f636", - "0x1fae5", - "0x1f636", - "0x200d", - "0x1f32b", - "0xfe0f", - "0x1f60f", - "0x1f612", - "0x1f644", - "0x1f62c", - "0x1f62e", - "0x200d", - "0x1f4a8", - "0x1f925", - "0x1fae8", - "0x1f642", - "0x200d", - "0x2194", - "0xfe0f", - "0x1f642", - "0x200d", - "0x2195", - "0xfe0f", - "0x1f60c", - "0x1f614", - "0x1f62a", - "0x1f924", - "0x1f634", - "0x1f637", - "0x1f912", - "0x1f915", - "0x1f922", - "0x1f92e", - "0x1f927", - "0x1f975", - "0x1f976", - "0x1f974", - "0x1f635", - "0x1f635", - "0x200d", - "0x1f4ab", - "0x1f92f", - "0x1f920", - "0x1f973", - "0x1f978", - "0x1f60e", - "0x1f913", - "0x1f9d0", - "0x1f615", - "0x1fae4", - "0x1f61f", - "0x1f641", - "0x2639", - "0x1f62e", - "0x1f62f", - "0x1f632", - "0x1f633", - "0x1f97a", - "0x1f979", - "0x1f626", - "0x1f627", - "0x1f628", - "0x1f630", - "0x1f625", - "0x1f622", - "0x1f62d", - "0x1f631", - "0x1f616", - "0x1f623", - "0x1f61e", - "0x1f613", - "0x1f629", - "0x1f62b", - "0x1f971", - "0x1f624", - "0x1f621", - "0x1f620", - "0x1f92c", - "0x1f608", - "0x1f47f", - "0x1f480", - "0x2620", - "0x1f4a9", - "0x1f921", - "0x1f479", - "0x1f47a", - "0x1f47b", - "0x1f47d", - "0x1f47e", - "0x1f916", - "0x1f63a", - "0x1f638", - "0x1f639", - "0x1f63b", - "0x1f63c", - "0x1f63d", - "0x1f640", - "0x1f63f", - "0x1f63e", - "0x1f648", - "0x1f649", - "0x1f64a", - "0x1f48c", - "0x1f498", - "0x1f49d", - "0x1f496", - "0x1f497", - "0x1f493", - "0x1f49e", - "0x1f495", - "0x1f49f", - "0x2763", - "0x1f494", - "0x2764", - "0xfe0f", - "0x200d", - "0x1f525", - "0x2764", - "0xfe0f", - "0x200d", - "0x1fa79", - "0x2764", - "0x1fa77", - "0x1f9e1", - "0x1f49b", - "0x1f49a", - "0x1f499", - "0x1fa75", - "0x1f49c", - "0x1f90e", - "0x1f5a4", - "0x1fa76", - "0x1f90d", - "0x1f48b", - "0x1f4af", - "0x1f4a2", - "0x1f4a5", - "0x1f4ab", - "0x1f4a6", - "0x1f4a8", - "0x1f573", - "0x1f4ac", - "0x1f441", - "0xfe0f", - "0x200d", - "0x1f5e8", - "0xfe0f", - "0x1f5e8", - "0x1f5ef", - "0x1f4ad", - "0x1f4a4" - ], - "people_and_body": [ - "0x1f44b", - "0x1f91a", - "0x1f590", - "0x270b", - "0x1f596", - "0x1faf1", - "0x1faf2", - "0x1faf3", - "0x1faf4", - "0x1faf7", - "0x1faf8", - "0x1f44c", - "0x1f90c", - "0x1f90f", - "0x270c", - "0x1f91e", - "0x1faf0", - "0x1f91f", - "0x1f918", - "0x1f919", - "0x1f448", - "0x1f449", - "0x1f446", - "0x1f595", - "0x1f447", - "0x261d", - "0x1faf5", - "0x1f44d", - "0x1f44e", - "0x270a", - "0x1f44a", - "0x1f91b", - "0x1f91c", - "0x1f44f", - "0x1f64c", - "0x1faf6", - "0x1f450", - "0x1f932", - "0x1f91d", - "0x1f64f", - "0x270d", - "0x1f485", - "0x1f933", - "0x1f4aa", - "0x1f9be", - "0x1f9bf", - "0x1f9b5", - "0x1f9b6", - "0x1f442", - "0x1f9bb", - "0x1f443", - "0x1f9e0", - "0x1fac0", - "0x1fac1", - "0x1f9b7", - "0x1f9b4", - "0x1f440", - "0x1f441", - "0x1f445", - "0x1f444", - "0x1fae6", - "0x1f476", - "0x1f9d2", - "0x1f466", - "0x1f467", - "0x1f9d1", - "0x1f471", - "0x1f468", - "0x1f9d4", - "0x1f9d4", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9d4", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f468", - "0x200d", - "0x1f9b0", - "0x1f468", - "0x200d", - "0x1f9b1", - "0x1f468", - "0x200d", - "0x1f9b3", - "0x1f468", - "0x200d", - "0x1f9b2", - "0x1f469", - "0x1f469", - "0x200d", - "0x1f9b0", - "0x1f9d1", - "0x200d", - "0x1f9b0", - "0x1f469", - "0x200d", - "0x1f9b1", - "0x1f9d1", - "0x200d", - "0x1f9b1", - "0x1f469", - "0x200d", - "0x1f9b3", - "0x1f9d1", - "0x200d", - "0x1f9b3", - "0x1f469", - "0x200d", - "0x1f9b2", - "0x1f9d1", - "0x200d", - "0x1f9b2", - "0x1f471", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f471", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9d3", - "0x1f474", - "0x1f475", - "0x1f64d", - "0x1f64d", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f64d", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f64e", - "0x1f64e", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f64e", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f645", - "0x1f645", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f645", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f646", - "0x1f646", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f646", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f481", - "0x1f481", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f481", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f64b", - "0x1f64b", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f64b", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9cf", - "0x1f9cf", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9cf", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f647", - "0x1f647", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f647", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f926", - "0x1f926", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f926", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f937", - "0x1f937", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f937", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9d1", - "0x200d", - "0x2695", - "0xfe0f", - "0x1f468", - "0x200d", - "0x2695", - "0xfe0f", - "0x1f469", - "0x200d", - "0x2695", - "0xfe0f", - "0x1f9d1", - "0x200d", - "0x1f393", - "0x1f468", - "0x200d", - "0x1f393", - "0x1f469", - "0x200d", - "0x1f393", - "0x1f9d1", - "0x200d", - "0x1f3eb", - "0x1f468", - "0x200d", - "0x1f3eb", - "0x1f469", - "0x200d", - "0x1f3eb", - "0x1f9d1", - "0x200d", - "0x2696", - "0xfe0f", - "0x1f468", - "0x200d", - "0x2696", - "0xfe0f", - "0x1f469", - "0x200d", - "0x2696", - "0xfe0f", - "0x1f9d1", - "0x200d", - "0x1f33e", - "0x1f468", - "0x200d", - "0x1f33e", - "0x1f469", - "0x200d", - "0x1f33e", - "0x1f9d1", - "0x200d", - "0x1f373", - "0x1f468", - "0x200d", - "0x1f373", - "0x1f469", - "0x200d", - "0x1f373", - "0x1f9d1", - "0x200d", - "0x1f527", - "0x1f468", - "0x200d", - "0x1f527", - "0x1f469", - "0x200d", - "0x1f527", - "0x1f9d1", - "0x200d", - "0x1f3ed", - "0x1f468", - "0x200d", - "0x1f3ed", - "0x1f469", - "0x200d", - "0x1f3ed", - "0x1f9d1", - "0x200d", - "0x1f4bc", - "0x1f468", - "0x200d", - "0x1f4bc", - "0x1f469", - "0x200d", - "0x1f4bc", - "0x1f9d1", - "0x200d", - "0x1f52c", - "0x1f468", - "0x200d", - "0x1f52c", - "0x1f469", - "0x200d", - "0x1f52c", - "0x1f9d1", - "0x200d", - "0x1f4bb", - "0x1f468", - "0x200d", - "0x1f4bb", - "0x1f469", - "0x200d", - "0x1f4bb", - "0x1f9d1", - "0x200d", - "0x1f3a4", - "0x1f468", - "0x200d", - "0x1f3a4", - "0x1f469", - "0x200d", - "0x1f3a4", - "0x1f9d1", - "0x200d", - "0x1f3a8", - "0x1f468", - "0x200d", - "0x1f3a8", - "0x1f469", - "0x200d", - "0x1f3a8", - "0x1f9d1", - "0x200d", - "0x2708", - "0xfe0f", - "0x1f468", - "0x200d", - "0x2708", - "0xfe0f", - "0x1f469", - "0x200d", - "0x2708", - "0xfe0f", - "0x1f9d1", - "0x200d", - "0x1f680", - "0x1f468", - "0x200d", - "0x1f680", - "0x1f469", - "0x200d", - "0x1f680", - "0x1f9d1", - "0x200d", - "0x1f692", - "0x1f468", - "0x200d", - "0x1f692", - "0x1f469", - "0x200d", - "0x1f692", - "0x1f46e", - "0x1f46e", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f46e", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f575", - "0x1f575", - "0xfe0f", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f575", - "0xfe0f", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f482", - "0x1f482", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f482", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f977", - "0x1f477", - "0x1f477", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f477", - "0x200d", - "0x2640", - "0xfe0f", - "0x1fac5", - "0x1f934", - "0x1f478", - "0x1f473", - "0x1f473", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f473", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f472", - "0x1f9d5", - "0x1f935", - "0x1f935", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f935", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f470", - "0x1f470", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f470", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f930", - "0x1fac3", - "0x1fac4", - "0x1f931", - "0x1f469", - "0x200d", - "0x1f37c", - "0x1f468", - "0x200d", - "0x1f37c", - "0x1f9d1", - "0x200d", - "0x1f37c", - "0x1f47c", - "0x1f385", - "0x1f936", - "0x1f9d1", - "0x200d", - "0x1f384", - "0x1f9b8", - "0x1f9b8", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9b8", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9b9", - "0x1f9b9", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9b9", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9d9", - "0x1f9d9", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9d9", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9da", - "0x1f9da", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9da", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9db", - "0x1f9db", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9db", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9dc", - "0x1f9dc", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9dc", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9dd", - "0x1f9dd", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9dd", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9de", - "0x1f9de", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9de", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9df", - "0x1f9df", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9df", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9cc", - "0x1f486", - "0x1f486", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f486", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f487", - "0x1f487", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f487", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f6b6", - "0x1f6b6", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f6b6", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f6b6", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f6b6", - "0x200d", - "0x2640", - "0xfe0f", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f6b6", - "0x200d", - "0x2642", - "0xfe0f", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f9cd", - "0x1f9cd", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9cd", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9ce", - "0x1f9ce", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9ce", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9ce", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f9ce", - "0x200d", - "0x2640", - "0xfe0f", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f9ce", - "0x200d", - "0x2642", - "0xfe0f", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f9d1", - "0x200d", - "0x1f9af", - "0x1f9d1", - "0x200d", - "0x1f9af", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f468", - "0x200d", - "0x1f9af", - "0x1f468", - "0x200d", - "0x1f9af", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f469", - "0x200d", - "0x1f9af", - "0x1f469", - "0x200d", - "0x1f9af", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f9d1", - "0x200d", - "0x1f9bc", - "0x1f9d1", - "0x200d", - "0x1f9bc", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f468", - "0x200d", - "0x1f9bc", - "0x1f468", - "0x200d", - "0x1f9bc", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f469", - "0x200d", - "0x1f9bc", - "0x1f469", - "0x200d", - "0x1f9bc", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f9d1", - "0x200d", - "0x1f9bd", - "0x1f9d1", - "0x200d", - "0x1f9bd", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f468", - "0x200d", - "0x1f9bd", - "0x1f468", - "0x200d", - "0x1f9bd", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f469", - "0x200d", - "0x1f9bd", - "0x1f469", - "0x200d", - "0x1f9bd", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f3c3", - "0x1f3c3", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f3c3", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f3c3", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f3c3", - "0x200d", - "0x2640", - "0xfe0f", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f3c3", - "0x200d", - "0x2642", - "0xfe0f", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f483", - "0x1f57a", - "0x1f574", - "0x1f46f", - "0x1f46f", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f46f", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9d6", - "0x1f9d6", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9d6", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9d7", - "0x1f9d7", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9d7", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f93a", - "0x1f3c7", - "0x26f7", - "0x1f3c2", - "0x1f3cc", - "0x1f3cc", - "0xfe0f", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f3cc", - "0xfe0f", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f3c4", - "0x1f3c4", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f3c4", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f6a3", - "0x1f6a3", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f6a3", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f3ca", - "0x1f3ca", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f3ca", - "0x200d", - "0x2640", - "0xfe0f", - "0x26f9", - "0x26f9", - "0xfe0f", - "0x200d", - "0x2642", - "0xfe0f", - "0x26f9", - "0xfe0f", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f3cb", - "0x1f3cb", - "0xfe0f", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f3cb", - "0xfe0f", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f6b4", - "0x1f6b4", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f6b4", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f6b5", - "0x1f6b5", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f6b5", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f938", - "0x1f938", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f938", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f93c", - "0x1f93c", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f93c", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f93d", - "0x1f93d", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f93d", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f93e", - "0x1f93e", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f93e", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f939", - "0x1f939", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f939", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9d8", - "0x1f9d8", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9d8", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f6c0", - "0x1f6cc", - "0x1f9d1", - "0x200d", - "0x1f91d", - "0x200d", - "0x1f9d1", - "0x1f46d", - "0x1f46b", - "0x1f46c", - "0x1f48f", - "0x1f469", - "0x200d", - "0x2764", - "0xfe0f", - "0x200d", - "0x1f48b", - "0x200d", - "0x1f468", - "0x1f468", - "0x200d", - "0x2764", - "0xfe0f", - "0x200d", - "0x1f48b", - "0x200d", - "0x1f468", - "0x1f469", - "0x200d", - "0x2764", - "0xfe0f", - "0x200d", - "0x1f48b", - "0x200d", - "0x1f469", - "0x1f491", - "0x1f469", - "0x200d", - "0x2764", - "0xfe0f", - "0x200d", - "0x1f468", - "0x1f468", - "0x200d", - "0x2764", - "0xfe0f", - "0x200d", - "0x1f468", - "0x1f469", - "0x200d", - "0x2764", - "0xfe0f", - "0x200d", - "0x1f469", - "0x1f468", - "0x200d", - "0x1f469", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f469", - "0x200d", - "0x1f467", - "0x1f468", - "0x200d", - "0x1f469", - "0x200d", - "0x1f467", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f469", - "0x200d", - "0x1f466", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f469", - "0x200d", - "0x1f467", - "0x200d", - "0x1f467", - "0x1f468", - "0x200d", - "0x1f468", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f468", - "0x200d", - "0x1f467", - "0x1f468", - "0x200d", - "0x1f468", - "0x200d", - "0x1f467", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f468", - "0x200d", - "0x1f466", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f468", - "0x200d", - "0x1f467", - "0x200d", - "0x1f467", - "0x1f469", - "0x200d", - "0x1f469", - "0x200d", - "0x1f466", - "0x1f469", - "0x200d", - "0x1f469", - "0x200d", - "0x1f467", - "0x1f469", - "0x200d", - "0x1f469", - "0x200d", - "0x1f467", - "0x200d", - "0x1f466", - "0x1f469", - "0x200d", - "0x1f469", - "0x200d", - "0x1f466", - "0x200d", - "0x1f466", - "0x1f469", - "0x200d", - "0x1f469", - "0x200d", - "0x1f467", - "0x200d", - "0x1f467", - "0x1f468", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f466", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f467", - "0x1f468", - "0x200d", - "0x1f467", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f467", - "0x200d", - "0x1f467", - "0x1f469", - "0x200d", - "0x1f466", - "0x1f469", - "0x200d", - "0x1f466", - "0x200d", - "0x1f466", - "0x1f469", - "0x200d", - "0x1f467", - "0x1f469", - "0x200d", - "0x1f467", - "0x200d", - "0x1f466", - "0x1f469", - "0x200d", - "0x1f467", - "0x200d", - "0x1f467", - "0x1f5e3", - "0x1f464", - "0x1f465", - "0x1fac2", - "0x1f46a", - "0x1f9d1", - "0x200d", - "0x1f9d1", - "0x200d", - "0x1f9d2", - "0x1f9d1", - "0x200d", - "0x1f9d1", - "0x200d", - "0x1f9d2", - "0x200d", - "0x1f9d2", - "0x1f9d1", - "0x200d", - "0x1f9d2", - "0x1f9d1", - "0x200d", - "0x1f9d2", - "0x200d", - "0x1f9d2", - "0x1f463" - ], - "animals_and_nature": [ - "0x1f435", - "0x1f412", - "0x1f98d", - "0x1f9a7", - "0x1f436", - "0x1f415", - "0x1f9ae", - "0x1f415", - "0x200d", - "0x1f9ba", - "0x1f429", - "0x1f43a", - "0x1f98a", - "0x1f99d", - "0x1f431", - "0x1f408", - "0x1f408", - "0x200d", - "0x2b1b", - "0x1f981", - "0x1f42f", - "0x1f405", - "0x1f406", - "0x1f434", - "0x1face", - "0x1facf", - "0x1f40e", - "0x1f984", - "0x1f993", - "0x1f98c", - "0x1f9ac", - "0x1f42e", - "0x1f402", - "0x1f403", - "0x1f404", - "0x1f437", - "0x1f416", - "0x1f417", - "0x1f43d", - "0x1f40f", - "0x1f411", - "0x1f410", - "0x1f42a", - "0x1f42b", - "0x1f999", - "0x1f992", - "0x1f418", - "0x1f9a3", - "0x1f98f", - "0x1f99b", - "0x1f42d", - "0x1f401", - "0x1f400", - "0x1f439", - "0x1f430", - "0x1f407", - "0x1f43f", - "0x1f9ab", - "0x1f994", - "0x1f987", - "0x1f43b", - "0x1f43b", - "0x200d", - "0x2744", - "0xfe0f", - "0x1f428", - "0x1f43c", - "0x1f9a5", - "0x1f9a6", - "0x1f9a8", - "0x1f998", - "0x1f9a1", - "0x1f43e", - "0x1f983", - "0x1f414", - "0x1f413", - "0x1f423", - "0x1f424", - "0x1f425", - "0x1f426", - "0x1f427", - "0x1f54a", - "0x1f985", - "0x1f986", - "0x1f9a2", - "0x1f989", - "0x1f9a4", - "0x1fab6", - "0x1f9a9", - "0x1f99a", - "0x1f99c", - "0x1fabd", - "0x1f426", - "0x200d", - "0x2b1b", - "0x1fabf", - "0x1f426", - "0x200d", - "0x1f525", - "0x1f438", - "0x1f40a", - "0x1f422", - "0x1f98e", - "0x1f40d", - "0x1f432", - "0x1f409", - "0x1f995", - "0x1f996", - "0x1f433", - "0x1f40b", - "0x1f42c", - "0x1f9ad", - "0x1f41f", - "0x1f420", - "0x1f421", - "0x1f988", - "0x1f419", - "0x1f41a", - "0x1fab8", - "0x1fabc", - "0x1f40c", - "0x1f98b", - "0x1f41b", - "0x1f41c", - "0x1f41d", - "0x1fab2", - "0x1f41e", - "0x1f997", - "0x1fab3", - "0x1f577", - "0x1f578", - "0x1f982", - "0x1f99f", - "0x1fab0", - "0x1fab1", - "0x1f9a0", - "0x1f490", - "0x1f338", - "0x1f4ae", - "0x1fab7", - "0x1f3f5", - "0x1f339", - "0x1f940", - "0x1f33a", - "0x1f33b", - "0x1f33c", - "0x1f337", - "0x1fabb", - "0x1f331", - "0x1fab4", - "0x1f332", - "0x1f333", - "0x1f334", - "0x1f335", - "0x1f33e", - "0x1f33f", - "0x2618", - "0x1f340", - "0x1f341", - "0x1f342", - "0x1f343", - "0x1fab9", - "0x1faba", - "0x1f344" - ], - "food_and_drink": [ - "0x1f347", - "0x1f348", - "0x1f349", - "0x1f34a", - "0x1f34b", - "0x1f34b", - "0x200d", - "0x1f7e9", - "0x1f34c", - "0x1f34d", - "0x1f96d", - "0x1f34e", - "0x1f34f", - "0x1f350", - "0x1f351", - "0x1f352", - "0x1f353", - "0x1fad0", - "0x1f95d", - "0x1f345", - "0x1fad2", - "0x1f965", - "0x1f951", - "0x1f346", - "0x1f954", - "0x1f955", - "0x1f33d", - "0x1f336", - "0x1fad1", - "0x1f952", - "0x1f96c", - "0x1f966", - "0x1f9c4", - "0x1f9c5", - "0x1f95c", - "0x1fad8", - "0x1f330", - "0x1fada", - "0x1fadb", - "0x1f344", - "0x200d", - "0x1f7eb", - "0x1f35e", - "0x1f950", - "0x1f956", - "0x1fad3", - "0x1f968", - "0x1f96f", - "0x1f95e", - "0x1f9c7", - "0x1f9c0", - "0x1f356", - "0x1f357", - "0x1f969", - "0x1f953", - "0x1f354", - "0x1f35f", - "0x1f355", - "0x1f32d", - "0x1f96a", - "0x1f32e", - "0x1f32f", - "0x1fad4", - "0x1f959", - "0x1f9c6", - "0x1f95a", - "0x1f373", - "0x1f958", - "0x1f372", - "0x1fad5", - "0x1f963", - "0x1f957", - "0x1f37f", - "0x1f9c8", - "0x1f9c2", - "0x1f96b", - "0x1f371", - "0x1f358", - "0x1f359", - "0x1f35a", - "0x1f35b", - "0x1f35c", - "0x1f35d", - "0x1f360", - "0x1f362", - "0x1f363", - "0x1f364", - "0x1f365", - "0x1f96e", - "0x1f361", - "0x1f95f", - "0x1f960", - "0x1f961", - "0x1f980", - "0x1f99e", - "0x1f990", - "0x1f991", - "0x1f9aa", - "0x1f366", - "0x1f367", - "0x1f368", - "0x1f369", - "0x1f36a", - "0x1f382", - "0x1f370", - "0x1f9c1", - "0x1f967", - "0x1f36b", - "0x1f36c", - "0x1f36d", - "0x1f36e", - "0x1f36f", - "0x1f37c", - "0x1f95b", - "0x2615", - "0x1fad6", - "0x1f375", - "0x1f376", - "0x1f37e", - "0x1f377", - "0x1f378", - "0x1f379", - "0x1f37a", - "0x1f37b", - "0x1f942", - "0x1f943", - "0x1fad7", - "0x1f964", - "0x1f9cb", - "0x1f9c3", - "0x1f9c9", - "0x1f9ca", - "0x1f962", - "0x1f37d", - "0x1f374", - "0x1f944", - "0x1f52a", - "0x1fad9", - "0x1f3fa" - ], - "travel_and_places": [ - "0x1f30d", - "0x1f30e", - "0x1f30f", - "0x1f310", - "0x1f5fa", - "0x1f5fe", - "0x1f9ed", - "0x1f3d4", - "0x26f0", - "0x1f30b", - "0x1f5fb", - "0x1f3d5", - "0x1f3d6", - "0x1f3dc", - "0x1f3dd", - "0x1f3de", - "0x1f3df", - "0x1f3db", - "0x1f3d7", - "0x1f9f1", - "0x1faa8", - "0x1fab5", - "0x1f6d6", - "0x1f3d8", - "0x1f3da", - "0x1f3e0", - "0x1f3e1", - "0x1f3e2", - "0x1f3e3", - "0x1f3e4", - "0x1f3e5", - "0x1f3e6", - "0x1f3e8", - "0x1f3e9", - "0x1f3ea", - "0x1f3eb", - "0x1f3ec", - "0x1f3ed", - "0x1f3ef", - "0x1f3f0", - "0x1f492", - "0x1f5fc", - "0x1f5fd", - "0x26ea", - "0x1f54c", - "0x1f6d5", - "0x1f54d", - "0x26e9", - "0x1f54b", - "0x26f2", - "0x26fa", - "0x1f301", - "0x1f303", - "0x1f3d9", - "0x1f304", - "0x1f305", - "0x1f306", - "0x1f307", - "0x1f309", - "0x2668", - "0x1f3a0", - "0x1f6dd", - "0x1f3a1", - "0x1f3a2", - "0x1f488", - "0x1f3aa", - "0x1f682", - "0x1f683", - "0x1f684", - "0x1f685", - "0x1f686", - "0x1f687", - "0x1f688", - "0x1f689", - "0x1f68a", - "0x1f69d", - "0x1f69e", - "0x1f68b", - "0x1f68c", - "0x1f68d", - "0x1f68e", - "0x1f690", - "0x1f691", - "0x1f692", - "0x1f693", - "0x1f694", - "0x1f695", - "0x1f696", - "0x1f697", - "0x1f698", - "0x1f699", - "0x1f6fb", - "0x1f69a", - "0x1f69b", - "0x1f69c", - "0x1f3ce", - "0x1f3cd", - "0x1f6f5", - "0x1f9bd", - "0x1f9bc", - "0x1f6fa", - "0x1f6b2", - "0x1f6f4", - "0x1f6f9", - "0x1f6fc", - "0x1f68f", - "0x1f6e3", - "0x1f6e4", - "0x1f6e2", - "0x26fd", - "0x1f6de", - "0x1f6a8", - "0x1f6a5", - "0x1f6a6", - "0x1f6d1", - "0x1f6a7", - "0x2693", - "0x1f6df", - "0x26f5", - "0x1f6f6", - "0x1f6a4", - "0x1f6f3", - "0x26f4", - "0x1f6e5", - "0x1f6a2", - "0x2708", - "0x1f6e9", - "0x1f6eb", - "0x1f6ec", - "0x1fa82", - "0x1f4ba", - "0x1f681", - "0x1f69f", - "0x1f6a0", - "0x1f6a1", - "0x1f6f0", - "0x1f680", - "0x1f6f8", - "0x1f6ce", - "0x1f9f3", - "0x231b", - "0x23f3", - "0x231a", - "0x23f0", - "0x23f1", - "0x23f2", - "0x1f570", - "0x1f55b", - "0x1f567", - "0x1f550", - "0x1f55c", - "0x1f551", - "0x1f55d", - "0x1f552", - "0x1f55e", - "0x1f553", - "0x1f55f", - "0x1f554", - "0x1f560", - "0x1f555", - "0x1f561", - "0x1f556", - "0x1f562", - "0x1f557", - "0x1f563", - "0x1f558", - "0x1f564", - "0x1f559", - "0x1f565", - "0x1f55a", - "0x1f566", - "0x1f311", - "0x1f312", - "0x1f313", - "0x1f314", - "0x1f315", - "0x1f316", - "0x1f317", - "0x1f318", - "0x1f319", - "0x1f31a", - "0x1f31b", - "0x1f31c", - "0x1f321", - "0x2600", - "0x1f31d", - "0x1f31e", - "0x1fa90", - "0x2b50", - "0x1f31f", - "0x1f320", - "0x1f30c", - "0x2601", - "0x26c5", - "0x26c8", - "0x1f324", - "0x1f325", - "0x1f326", - "0x1f327", - "0x1f328", - "0x1f329", - "0x1f32a", - "0x1f32b", - "0x1f32c", - "0x1f300", - "0x1f308", - "0x1f302", - "0x2602", - "0x2614", - "0x26f1", - "0x26a1", - "0x2744", - "0x2603", - "0x26c4", - "0x2604", - "0x1f525", - "0x1f4a7", - "0x1f30a" - ], - "activities": [ - "0x1f383", - "0x1f384", - "0x1f386", - "0x1f387", - "0x1f9e8", - "0x2728", - "0x1f388", - "0x1f389", - "0x1f38a", - "0x1f38b", - "0x1f38d", - "0x1f38e", - "0x1f38f", - "0x1f390", - "0x1f391", - "0x1f9e7", - "0x1f380", - "0x1f381", - "0x1f397", - "0x1f39f", - "0x1f3ab", - "0x1f396", - "0x1f3c6", - "0x1f3c5", - "0x1f947", - "0x1f948", - "0x1f949", - "0x26bd", - "0x26be", - "0x1f94e", - "0x1f3c0", - "0x1f3d0", - "0x1f3c8", - "0x1f3c9", - "0x1f3be", - "0x1f94f", - "0x1f3b3", - "0x1f3cf", - "0x1f3d1", - "0x1f3d2", - "0x1f94d", - "0x1f3d3", - "0x1f3f8", - "0x1f94a", - "0x1f94b", - "0x1f945", - "0x26f3", - "0x26f8", - "0x1f3a3", - "0x1f93f", - "0x1f3bd", - "0x1f3bf", - "0x1f6f7", - "0x1f94c", - "0x1f3af", - "0x1fa80", - "0x1fa81", - "0x1f52b", - "0x1f3b1", - "0x1f52e", - "0x1fa84", - "0x1f3ae", - "0x1f579", - "0x1f3b0", - "0x1f3b2", - "0x1f9e9", - "0x1f9f8", - "0x1fa85", - "0x1faa9", - "0x1fa86", - "0x2660", - "0x2665", - "0x2666", - "0x2663", - "0x265f", - "0x1f0cf", - "0x1f004", - "0x1f3b4", - "0x1f3ad", - "0x1f5bc", - "0x1f3a8", - "0x1f9f5", - "0x1faa1", - "0x1f9f6", - "0x1faa2" - ], - "objects": [ - "0x1f453", - "0x1f576", - "0x1f97d", - "0x1f97c", - "0x1f9ba", - "0x1f454", - "0x1f455", - "0x1f456", - "0x1f9e3", - "0x1f9e4", - "0x1f9e5", - "0x1f9e6", - "0x1f457", - "0x1f458", - "0x1f97b", - "0x1fa71", - "0x1fa72", - "0x1fa73", - "0x1f459", - "0x1f45a", - "0x1faad", - "0x1f45b", - "0x1f45c", - "0x1f45d", - "0x1f6cd", - "0x1f392", - "0x1fa74", - "0x1f45e", - "0x1f45f", - "0x1f97e", - "0x1f97f", - "0x1f460", - "0x1f461", - "0x1fa70", - "0x1f462", - "0x1faae", - "0x1f451", - "0x1f452", - "0x1f3a9", - "0x1f393", - "0x1f9e2", - "0x1fa96", - "0x26d1", - "0x1f4ff", - "0x1f484", - "0x1f48d", - "0x1f48e", - "0x1f507", - "0x1f508", - "0x1f509", - "0x1f50a", - "0x1f4e2", - "0x1f4e3", - "0x1f4ef", - "0x1f514", - "0x1f515", - "0x1f3bc", - "0x1f3b5", - "0x1f3b6", - "0x1f399", - "0x1f39a", - "0x1f39b", - "0x1f3a4", - "0x1f3a7", - "0x1f4fb", - "0x1f3b7", - "0x1fa97", - "0x1f3b8", - "0x1f3b9", - "0x1f3ba", - "0x1f3bb", - "0x1fa95", - "0x1f941", - "0x1fa98", - "0x1fa87", - "0x1fa88", - "0x1f4f1", - "0x1f4f2", - "0x260e", - "0x1f4de", - "0x1f4df", - "0x1f4e0", - "0x1f50b", - "0x1faab", - "0x1f50c", - "0x1f4bb", - "0x1f5a5", - "0x1f5a8", - "0x2328", - "0x1f5b1", - "0x1f5b2", - "0x1f4bd", - "0x1f4be", - "0x1f4bf", - "0x1f4c0", - "0x1f9ee", - "0x1f3a5", - "0x1f39e", - "0x1f4fd", - "0x1f3ac", - "0x1f4fa", - "0x1f4f7", - "0x1f4f8", - "0x1f4f9", - "0x1f4fc", - "0x1f50d", - "0x1f50e", - "0x1f56f", - "0x1f4a1", - "0x1f526", - "0x1f3ee", - "0x1fa94", - "0x1f4d4", - "0x1f4d5", - "0x1f4d6", - "0x1f4d7", - "0x1f4d8", - "0x1f4d9", - "0x1f4da", - "0x1f4d3", - "0x1f4d2", - "0x1f4c3", - "0x1f4dc", - "0x1f4c4", - "0x1f4f0", - "0x1f5de", - "0x1f4d1", - "0x1f516", - "0x1f3f7", - "0x1f4b0", - "0x1fa99", - "0x1f4b4", - "0x1f4b5", - "0x1f4b6", - "0x1f4b7", - "0x1f4b8", - "0x1f4b3", - "0x1f9fe", - "0x1f4b9", - "0x2709", - "0x1f4e7", - "0x1f4e8", - "0x1f4e9", - "0x1f4e4", - "0x1f4e5", - "0x1f4e6", - "0x1f4eb", - "0x1f4ea", - "0x1f4ec", - "0x1f4ed", - "0x1f4ee", - "0x1f5f3", - "0x270f", - "0x2712", - "0x1f58b", - "0x1f58a", - "0x1f58c", - "0x1f58d", - "0x1f4dd", - "0x1f4bc", - "0x1f4c1", - "0x1f4c2", - "0x1f5c2", - "0x1f4c5", - "0x1f4c6", - "0x1f5d2", - "0x1f5d3", - "0x1f4c7", - "0x1f4c8", - "0x1f4c9", - "0x1f4ca", - "0x1f4cb", - "0x1f4cc", - "0x1f4cd", - "0x1f4ce", - "0x1f587", - "0x1f4cf", - "0x1f4d0", - "0x2702", - "0x1f5c3", - "0x1f5c4", - "0x1f5d1", - "0x1f512", - "0x1f513", - "0x1f50f", - "0x1f510", - "0x1f511", - "0x1f5dd", - "0x1f528", - "0x1fa93", - "0x26cf", - "0x2692", - "0x1f6e0", - "0x1f5e1", - "0x2694", - "0x1f4a3", - "0x1fa83", - "0x1f3f9", - "0x1f6e1", - "0x1fa9a", - "0x1f527", - "0x1fa9b", - "0x1f529", - "0x2699", - "0x1f5dc", - "0x2696", - "0x1f9af", - "0x1f517", - "0x26d3", - "0xfe0f", - "0x200d", - "0x1f4a5", - "0x26d3", - "0x1fa9d", - "0x1f9f0", - "0x1f9f2", - "0x1fa9c", - "0x2697", - "0x1f9ea", - "0x1f9eb", - "0x1f9ec", - "0x1f52c", - "0x1f52d", - "0x1f4e1", - "0x1f489", - "0x1fa78", - "0x1f48a", - "0x1fa79", - "0x1fa7c", - "0x1fa7a", - "0x1fa7b", - "0x1f6aa", - "0x1f6d7", - "0x1fa9e", - "0x1fa9f", - "0x1f6cf", - "0x1f6cb", - "0x1fa91", - "0x1f6bd", - "0x1faa0", - "0x1f6bf", - "0x1f6c1", - "0x1faa4", - "0x1fa92", - "0x1f9f4", - "0x1f9f7", - "0x1f9f9", - "0x1f9fa", - "0x1f9fb", - "0x1faa3", - "0x1f9fc", - "0x1fae7", - "0x1faa5", - "0x1f9fd", - "0x1f9ef", - "0x1f6d2", - "0x1f6ac", - "0x26b0", - "0x1faa6", - "0x26b1", - "0x1f9ff", - "0x1faac", - "0x1f5ff", - "0x1faa7", - "0x1faaa" - ], - "symbols": [ - "0x1f3e7", - "0x1f6ae", - "0x1f6b0", - "0x267f", - "0x1f6b9", - "0x1f6ba", - "0x1f6bb", - "0x1f6bc", - "0x1f6be", - "0x1f6c2", - "0x1f6c3", - "0x1f6c4", - "0x1f6c5", - "0x26a0", - "0x1f6b8", - "0x26d4", - "0x1f6ab", - "0x1f6b3", - "0x1f6ad", - "0x1f6af", - "0x1f6b1", - "0x1f6b7", - "0x1f4f5", - "0x1f51e", - "0x2622", - "0x2623", - "0x2b06", - "0x2197", - "0x27a1", - "0x2198", - "0x2b07", - "0x2199", - "0x2b05", - "0x2196", - "0x2195", - "0x2194", - "0x21a9", - "0x21aa", - "0x2934", - "0x2935", - "0x1f503", - "0x1f504", - "0x1f519", - "0x1f51a", - "0x1f51b", - "0x1f51c", - "0x1f51d", - "0x1f6d0", - "0x269b", - "0x1f549", - "0x2721", - "0x2638", - "0x262f", - "0x271d", - "0x2626", - "0x262a", - "0x262e", - "0x1f54e", - "0x1f52f", - "0x1faaf", - "0x2648", - "0x2649", - "0x264a", - "0x264b", - "0x264c", - "0x264d", - "0x264e", - "0x264f", - "0x2650", - "0x2651", - "0x2652", - "0x2653", - "0x26ce", - "0x1f500", - "0x1f501", - "0x1f502", - "0x25b6", - "0x23e9", - "0x23ed", - "0x23ef", - "0x25c0", - "0x23ea", - "0x23ee", - "0x1f53c", - "0x23eb", - "0x1f53d", - "0x23ec", - "0x23f8", - "0x23f9", - "0x23fa", - "0x23cf", - "0x1f3a6", - "0x1f505", - "0x1f506", - "0x1f4f6", - "0x1f6dc", - "0x1f4f3", - "0x1f4f4", - "0x2640", - "0x2642", - "0x26a7", - "0x2716", - "0x2795", - "0x2796", - "0x2797", - "0x1f7f0", - "0x267e", - "0x203c", - "0x2049", - "0x2753", - "0x2754", - "0x2755", - "0x2757", - "0x3030", - "0x1f4b1", - "0x1f4b2", - "0x2695", - "0x267b", - "0x269c", - "0x1f531", - "0x1f4db", - "0x1f530", - "0x2b55", - "0x2705", - "0x2611", - "0x2714", - "0x274c", - "0x274e", - "0x27b0", - "0x27bf", - "0x303d", - "0x2733", - "0x2734", - "0x2747", - "0x00a9", - "0x00ae", - "0x2122", - "0x0023", - "0xfe0f", - "0x20e3", - "0x002a", - "0xfe0f", - "0x20e3", - "0x0030", - "0xfe0f", - "0x20e3", - "0x0031", - "0xfe0f", - "0x20e3", - "0x0032", - "0xfe0f", - "0x20e3", - "0x0033", - "0xfe0f", - "0x20e3", - "0x0034", - "0xfe0f", - "0x20e3", - "0x0035", - "0xfe0f", - "0x20e3", - "0x0036", - "0xfe0f", - "0x20e3", - "0x0037", - "0xfe0f", - "0x20e3", - "0x0038", - "0xfe0f", - "0x20e3", - "0x0039", - "0xfe0f", - "0x20e3", - "0x1f51f", - "0x1f520", - "0x1f521", - "0x1f522", - "0x1f523", - "0x1f524", - "0x1f170", - "0x1f18e", - "0x1f171", - "0x1f191", - "0x1f192", - "0x1f193", - "0x2139", - "0x1f194", - "0x24c2", - "0x1f195", - "0x1f196", - "0x1f17e", - "0x1f197", - "0x1f17f", - "0x1f198", - "0x1f199", - "0x1f19a", - "0x1f201", - "0x1f202", - "0x1f237", - "0x1f236", - "0x1f22f", - "0x1f250", - "0x1f239", - "0x1f21a", - "0x1f232", - "0x1f251", - "0x1f238", - "0x1f234", - "0x1f233", - "0x3297", - "0x3299", - "0x1f23a", - "0x1f235", - "0x1f534", - "0x1f7e0", - "0x1f7e1", - "0x1f7e2", - "0x1f535", - "0x1f7e3", - "0x1f7e4", - "0x26ab", - "0x26aa", - "0x1f7e5", - "0x1f7e7", - "0x1f7e8", - "0x1f7e9", - "0x1f7e6", - "0x1f7ea", - "0x1f7eb", - "0x2b1b", - "0x2b1c", - "0x25fc", - "0x25fb", - "0x25fe", - "0x25fd", - "0x25aa", - "0x25ab", - "0x1f536", - "0x1f537", - "0x1f538", - "0x1f539", - "0x1f53a", - "0x1f53b", - "0x1f4a0", - "0x1f518", - "0x1f533", - "0x1f532" - ], - "flags": [ - "0x1f3c1", - "0x1f6a9", - "0x1f38c", - "0x1f3f4", - "0x1f3f3", - "0x1f3f3", - "0xfe0f", - "0x200d", - "0x1f308", - "0x1f3f3", - "0xfe0f", - "0x200d", - "0x26a7", - "0xfe0f", - "0x1f3f4", - "0x200d", - "0x2620", - "0xfe0f", - "0x1f1e6", - "0x1f1e8", - "0x1f1e6", - "0x1f1e9", - "0x1f1e6", - "0x1f1ea", - "0x1f1e6", - "0x1f1eb", - "0x1f1e6", - "0x1f1ec", - "0x1f1e6", - "0x1f1ee", - "0x1f1e6", - "0x1f1f1", - "0x1f1e6", - "0x1f1f2", - "0x1f1e6", - "0x1f1f4", - "0x1f1e6", - "0x1f1f6", - "0x1f1e6", - "0x1f1f7", - "0x1f1e6", - "0x1f1f8", - "0x1f1e6", - "0x1f1f9", - "0x1f1e6", - "0x1f1fa", - "0x1f1e6", - "0x1f1fc", - "0x1f1e6", - "0x1f1fd", - "0x1f1e6", - "0x1f1ff", - "0x1f1e7", - "0x1f1e6", - "0x1f1e7", - "0x1f1e7", - "0x1f1e7", - "0x1f1e9", - "0x1f1e7", - "0x1f1ea", - "0x1f1e7", - "0x1f1eb", - "0x1f1e7", - "0x1f1ec", - "0x1f1e7", - "0x1f1ed", - "0x1f1e7", - "0x1f1ee", - "0x1f1e7", - "0x1f1ef", - "0x1f1e7", - "0x1f1f1", - "0x1f1e7", - "0x1f1f2", - "0x1f1e7", - "0x1f1f3", - "0x1f1e7", - "0x1f1f4", - "0x1f1e7", - "0x1f1f6", - "0x1f1e7", - "0x1f1f7", - "0x1f1e7", - "0x1f1f8", - "0x1f1e7", - "0x1f1f9", - "0x1f1e7", - "0x1f1fb", - "0x1f1e7", - "0x1f1fc", - "0x1f1e7", - "0x1f1fe", - "0x1f1e7", - "0x1f1ff", - "0x1f1e8", - "0x1f1e6", - "0x1f1e8", - "0x1f1e8", - "0x1f1e8", - "0x1f1e9", - "0x1f1e8", - "0x1f1eb", - "0x1f1e8", - "0x1f1ec", - "0x1f1e8", - "0x1f1ed", - "0x1f1e8", - "0x1f1ee", - "0x1f1e8", - "0x1f1f0", - "0x1f1e8", - "0x1f1f1", - "0x1f1e8", - "0x1f1f2", - "0x1f1e8", - "0x1f1f3", - "0x1f1e8", - "0x1f1f4", - "0x1f1e8", - "0x1f1f5", - "0x1f1e8", - "0x1f1f7", - "0x1f1e8", - "0x1f1fa", - "0x1f1e8", - "0x1f1fb", - "0x1f1e8", - "0x1f1fc", - "0x1f1e8", - "0x1f1fd", - "0x1f1e8", - "0x1f1fe", - "0x1f1e8", - "0x1f1ff", - "0x1f1e9", - "0x1f1ea", - "0x1f1e9", - "0x1f1ec", - "0x1f1e9", - "0x1f1ef", - "0x1f1e9", - "0x1f1f0", - "0x1f1e9", - "0x1f1f2", - "0x1f1e9", - "0x1f1f4", - "0x1f1e9", - "0x1f1ff", - "0x1f1ea", - "0x1f1e6", - "0x1f1ea", - "0x1f1e8", - "0x1f1ea", - "0x1f1ea", - "0x1f1ea", - "0x1f1ec", - "0x1f1ea", - "0x1f1ed", - "0x1f1ea", - "0x1f1f7", - "0x1f1ea", - "0x1f1f8", - "0x1f1ea", - "0x1f1f9", - "0x1f1ea", - "0x1f1fa", - "0x1f1eb", - "0x1f1ee", - "0x1f1eb", - "0x1f1ef", - "0x1f1eb", - "0x1f1f0", - "0x1f1eb", - "0x1f1f2", - "0x1f1eb", - "0x1f1f4", - "0x1f1eb", - "0x1f1f7", - "0x1f1ec", - "0x1f1e6", - "0x1f1ec", - "0x1f1e7", - "0x1f1ec", - "0x1f1e9", - "0x1f1ec", - "0x1f1ea", - "0x1f1ec", - "0x1f1eb", - "0x1f1ec", - "0x1f1ec", - "0x1f1ec", - "0x1f1ed", - "0x1f1ec", - "0x1f1ee", - "0x1f1ec", - "0x1f1f1", - "0x1f1ec", - "0x1f1f2", - "0x1f1ec", - "0x1f1f3", - "0x1f1ec", - "0x1f1f5", - "0x1f1ec", - "0x1f1f6", - "0x1f1ec", - "0x1f1f7", - "0x1f1ec", - "0x1f1f8", - "0x1f1ec", - "0x1f1f9", - "0x1f1ec", - "0x1f1fa", - "0x1f1ec", - "0x1f1fc", - "0x1f1ec", - "0x1f1fe", - "0x1f1ed", - "0x1f1f0", - "0x1f1ed", - "0x1f1f2", - "0x1f1ed", - "0x1f1f3", - "0x1f1ed", - "0x1f1f7", - "0x1f1ed", - "0x1f1f9", - "0x1f1ed", - "0x1f1fa", - "0x1f1ee", - "0x1f1e8", - "0x1f1ee", - "0x1f1e9", - "0x1f1ee", - "0x1f1ea", - "0x1f1ee", - "0x1f1f1", - "0x1f1ee", - "0x1f1f2", - "0x1f1ee", - "0x1f1f3", - "0x1f1ee", - "0x1f1f4", - "0x1f1ee", - "0x1f1f6", - "0x1f1ee", - "0x1f1f7", - "0x1f1ee", - "0x1f1f8", - "0x1f1ee", - "0x1f1f9", - "0x1f1ef", - "0x1f1ea", - "0x1f1ef", - "0x1f1f2", - "0x1f1ef", - "0x1f1f4", - "0x1f1ef", - "0x1f1f5", - "0x1f1f0", - "0x1f1ea", - "0x1f1f0", - "0x1f1ec", - "0x1f1f0", - "0x1f1ed", - "0x1f1f0", - "0x1f1ee", - "0x1f1f0", - "0x1f1f2", - "0x1f1f0", - "0x1f1f3", - "0x1f1f0", - "0x1f1f5", - "0x1f1f0", - "0x1f1f7", - "0x1f1f0", - "0x1f1fc", - "0x1f1f0", - "0x1f1fe", - "0x1f1f0", - "0x1f1ff", - "0x1f1f1", - "0x1f1e6", - "0x1f1f1", - "0x1f1e7", - "0x1f1f1", - "0x1f1e8", - "0x1f1f1", - "0x1f1ee", - "0x1f1f1", - "0x1f1f0", - "0x1f1f1", - "0x1f1f7", - "0x1f1f1", - "0x1f1f8", - "0x1f1f1", - "0x1f1f9", - "0x1f1f1", - "0x1f1fa", - "0x1f1f1", - "0x1f1fb", - "0x1f1f1", - "0x1f1fe", - "0x1f1f2", - "0x1f1e6", - "0x1f1f2", - "0x1f1e8", - "0x1f1f2", - "0x1f1e9", - "0x1f1f2", - "0x1f1ea", - "0x1f1f2", - "0x1f1eb", - "0x1f1f2", - "0x1f1ec", - "0x1f1f2", - "0x1f1ed", - "0x1f1f2", - "0x1f1f0", - "0x1f1f2", - "0x1f1f1", - "0x1f1f2", - "0x1f1f2", - "0x1f1f2", - "0x1f1f3", - "0x1f1f2", - "0x1f1f4", - "0x1f1f2", - "0x1f1f5", - "0x1f1f2", - "0x1f1f6", - "0x1f1f2", - "0x1f1f7", - "0x1f1f2", - "0x1f1f8", - "0x1f1f2", - "0x1f1f9", - "0x1f1f2", - "0x1f1fa", - "0x1f1f2", - "0x1f1fb", - "0x1f1f2", - "0x1f1fc", - "0x1f1f2", - "0x1f1fd", - "0x1f1f2", - "0x1f1fe", - "0x1f1f2", - "0x1f1ff", - "0x1f1f3", - "0x1f1e6", - "0x1f1f3", - "0x1f1e8", - "0x1f1f3", - "0x1f1ea", - "0x1f1f3", - "0x1f1eb", - "0x1f1f3", - "0x1f1ec", - "0x1f1f3", - "0x1f1ee", - "0x1f1f3", - "0x1f1f1", - "0x1f1f3", - "0x1f1f4", - "0x1f1f3", - "0x1f1f5", - "0x1f1f3", - "0x1f1f7", - "0x1f1f3", - "0x1f1fa", - "0x1f1f3", - "0x1f1ff", - "0x1f1f4", - "0x1f1f2", - "0x1f1f5", - "0x1f1e6", - "0x1f1f5", - "0x1f1ea", - "0x1f1f5", - "0x1f1eb", - "0x1f1f5", - "0x1f1ec", - "0x1f1f5", - "0x1f1ed", - "0x1f1f5", - "0x1f1f0", - "0x1f1f5", - "0x1f1f1", - "0x1f1f5", - "0x1f1f2", - "0x1f1f5", - "0x1f1f3", - "0x1f1f5", - "0x1f1f7", - "0x1f1f5", - "0x1f1f8", - "0x1f1f5", - "0x1f1f9", - "0x1f1f5", - "0x1f1fc", - "0x1f1f5", - "0x1f1fe", - "0x1f1f6", - "0x1f1e6", - "0x1f1f7", - "0x1f1ea", - "0x1f1f7", - "0x1f1f4", - "0x1f1f7", - "0x1f1f8", - "0x1f1f7", - "0x1f1fa", - "0x1f1f7", - "0x1f1fc", - "0x1f1f8", - "0x1f1e6", - "0x1f1f8", - "0x1f1e7", - "0x1f1f8", - "0x1f1e8", - "0x1f1f8", - "0x1f1e9", - "0x1f1f8", - "0x1f1ea", - "0x1f1f8", - "0x1f1ec", - "0x1f1f8", - "0x1f1ed", - "0x1f1f8", - "0x1f1ee", - "0x1f1f8", - "0x1f1ef", - "0x1f1f8", - "0x1f1f0", - "0x1f1f8", - "0x1f1f1", - "0x1f1f8", - "0x1f1f2", - "0x1f1f8", - "0x1f1f3", - "0x1f1f8", - "0x1f1f4", - "0x1f1f8", - "0x1f1f7", - "0x1f1f8", - "0x1f1f8", - "0x1f1f8", - "0x1f1f9", - "0x1f1f8", - "0x1f1fb", - "0x1f1f8", - "0x1f1fd", - "0x1f1f8", - "0x1f1fe", - "0x1f1f8", - "0x1f1ff", - "0x1f1f9", - "0x1f1e6", - "0x1f1f9", - "0x1f1e8", - "0x1f1f9", - "0x1f1e9", - "0x1f1f9", - "0x1f1eb", - "0x1f1f9", - "0x1f1ec", - "0x1f1f9", - "0x1f1ed", - "0x1f1f9", - "0x1f1ef", - "0x1f1f9", - "0x1f1f0", - "0x1f1f9", - "0x1f1f1", - "0x1f1f9", - "0x1f1f2", - "0x1f1f9", - "0x1f1f3", - "0x1f1f9", - "0x1f1f4", - "0x1f1f9", - "0x1f1f7", - "0x1f1f9", - "0x1f1f9", - "0x1f1f9", - "0x1f1fb", - "0x1f1f9", - "0x1f1fc", - "0x1f1f9", - "0x1f1ff", - "0x1f1fa", - "0x1f1e6", - "0x1f1fa", - "0x1f1ec", - "0x1f1fa", - "0x1f1f2", - "0x1f1fa", - "0x1f1f3", - "0x1f1fa", - "0x1f1f8", - "0x1f1fa", - "0x1f1fe", - "0x1f1fa", - "0x1f1ff", - "0x1f1fb", - "0x1f1e6", - "0x1f1fb", - "0x1f1e8", - "0x1f1fb", - "0x1f1ea", - "0x1f1fb", - "0x1f1ec", - "0x1f1fb", - "0x1f1ee", - "0x1f1fb", - "0x1f1f3", - "0x1f1fb", - "0x1f1fa", - "0x1f1fc", - "0x1f1eb", - "0x1f1fc", - "0x1f1f8", - "0x1f1fd", - "0x1f1f0", - "0x1f1fe", - "0x1f1ea", - "0x1f1fe", - "0x1f1f9", - "0x1f1ff", - "0x1f1e6", - "0x1f1ff", - "0x1f1f2", - "0x1f1ff", - "0x1f1fc", - "0x1f3f4", - "0xe0067", - "0xe0062", - "0xe0065", - "0xe006e", - "0xe0067", - "0xe007f", - "0x1f3f4", - "0xe0067", - "0xe0062", - "0xe0073", - "0xe0063", - "0xe0074", - "0xe007f", - "0x1f3f4", - "0xe0067", - "0xe0062", - "0xe0077", - "0xe006c", - "0xe0073", - "0xe007f" - ] - } - }; - - var o_hasOwnProperty = Object.prototype.hasOwnProperty; - var o_keys = (Object.keys || function(obj) { - var result = []; - for (var key in obj) { - if (o_hasOwnProperty.call(obj, key)) { - result.push(key); - } - } - - return result; - }); - - - function _copyObject(source, target) { - var keys = o_keys(source); - var key; - - for (var i = 0, l = keys.length; i < l; i++) { - key = keys[i]; - target[key] = source[key] || target[key]; - } - } - - function _copyArray(source, target) { - for (var i = 0, l = source.length; i < l; i++) { - target[i] = source[i]; - } - } - - function copyObject(source, _target) { - var isArray = Array.isArray(source); - var target = _target || (isArray ? new Array(source.length) : {}); - - if (isArray) { - _copyArray(source, target); - } else { - _copyObject(source, target); - } - - return target; - } - - /** Get the data based on key**/ - Chance.prototype.get = function (name) { - return copyObject(data[name]); - }; - - // Mac Address - Chance.prototype.mac_address = function(options){ - // typically mac addresses are separated by ":" - // however they can also be separated by "-" - // the network variant uses a dot every fourth byte - - options = initOptions(options); - if(!options.separator) { - options.separator = options.networkVersion ? "." : ":"; - } - - var mac_pool="ABCDEF1234567890", - mac = ""; - if(!options.networkVersion) { - mac = this.n(this.string, 6, { pool: mac_pool, length:2 }).join(options.separator); - } else { - mac = this.n(this.string, 3, { pool: mac_pool, length:4 }).join(options.separator); - } - - return mac; - }; - - Chance.prototype.normal = function (options) { - options = initOptions(options, {mean : 0, dev : 1, pool : []}); - - testRange( - options.pool.constructor !== Array, - "Chance: The pool option must be a valid array." - ); - testRange( - typeof options.mean !== 'number', - "Chance: Mean (mean) must be a number" - ); - testRange( - typeof options.dev !== 'number', - "Chance: Standard deviation (dev) must be a number" - ); - - // If a pool has been passed, then we are returning an item from that pool, - // using the normal distribution settings that were passed in - if (options.pool.length > 0) { - return this.normal_pool(options); - } - - // The Marsaglia Polar method - var s, u, v, norm, - mean = options.mean, - dev = options.dev; - - do { - // U and V are from the uniform distribution on (-1, 1) - u = this.random() * 2 - 1; - v = this.random() * 2 - 1; - - s = u * u + v * v; - } while (s >= 1); - - // Compute the standard normal variate - norm = u * Math.sqrt(-2 * Math.log(s) / s); - - // Shape and scale - return dev * norm + mean; - }; - - Chance.prototype.normal_pool = function(options) { - var performanceCounter = 0; - do { - var idx = Math.round(this.normal({ mean: options.mean, dev: options.dev })); - if (idx < options.pool.length && idx >= 0) { - return options.pool[idx]; - } else { - performanceCounter++; - } - } while(performanceCounter < 100); - - throw new RangeError("Chance: Your pool is too small for the given mean and standard deviation. Please adjust."); - }; - - Chance.prototype.radio = function (options) { - // Initial Letter (Typically Designated by Side of Mississippi River) - options = initOptions(options, {side : "?"}); - var fl = ""; - switch (options.side.toLowerCase()) { - case "east": - case "e": - fl = "W"; - break; - case "west": - case "w": - fl = "K"; - break; - default: - fl = this.character({pool: "KW"}); - break; - } - - return fl + this.character({alpha: true, casing: "upper"}) + - this.character({alpha: true, casing: "upper"}) + - this.character({alpha: true, casing: "upper"}); - }; - - // Set the data as key and data or the data map - Chance.prototype.set = function (name, values) { - if (typeof name === "string") { - data[name] = values; - } else { - data = copyObject(name, data); - } - }; - - Chance.prototype.tv = function (options) { - return this.radio(options); - }; - - // ID number for Brazil companies - Chance.prototype.cnpj = function () { - var n = this.n(this.natural, 8, { max: 9 }); - var d1 = 2+n[7]*6+n[6]*7+n[5]*8+n[4]*9+n[3]*2+n[2]*3+n[1]*4+n[0]*5; - d1 = 11 - (d1 % 11); - if (d1>=10){ - d1 = 0; - } - var d2 = d1*2+3+n[7]*7+n[6]*8+n[5]*9+n[4]*2+n[3]*3+n[2]*4+n[1]*5+n[0]*6; - d2 = 11 - (d2 % 11); - if (d2>=10){ - d2 = 0; - } - return ''+n[0]+n[1]+'.'+n[2]+n[3]+n[4]+'.'+n[5]+n[6]+n[7]+'/0001-'+d1+d2; - }; - - Chance.prototype.emotion = function () { - return this.pick(this.get("emotions")); - }; - - // -- End Miscellaneous -- - - Chance.prototype.mersenne_twister = function (seed) { - return new MersenneTwister(seed); - }; - - Chance.prototype.blueimp_md5 = function () { - return new BlueImpMD5(); - }; - - // Mersenne Twister from https://gist.github.com/banksean/300494 - /* - A C-program for MT19937, with initialization improved 2002/1/26. - Coded by Takuji Nishimura and Makoto Matsumoto. - - Before using, initialize the state by using init_genrand(seed) - or init_by_array(init_key, key_length). - - Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. The names of its contributors may not be used to endorse or promote - products derived from this software without specific prior written - permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - Any feedback is very welcome. - http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html - email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) - */ - var MersenneTwister = function (seed) { - if (seed === undefined) { - // kept random number same size as time used previously to ensure no unexpected results downstream - seed = Math.floor(Math.random()*Math.pow(10,13)); - } - /* Period parameters */ - this.N = 624; - this.M = 397; - this.MATRIX_A = 0x9908b0df; /* constant vector a */ - this.UPPER_MASK = 0x80000000; /* most significant w-r bits */ - this.LOWER_MASK = 0x7fffffff; /* least significant r bits */ - - this.mt = new Array(this.N); /* the array for the state vector */ - this.mti = this.N + 1; /* mti==N + 1 means mt[N] is not initialized */ - - this.init_genrand(seed); - }; - - /* initializes mt[N] with a seed */ - MersenneTwister.prototype.init_genrand = function (s) { - this.mt[0] = s >>> 0; - for (this.mti = 1; this.mti < this.N; this.mti++) { - s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30); - this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253) + this.mti; - /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ - /* In the previous versions, MSBs of the seed affect */ - /* only MSBs of the array mt[]. */ - /* 2002/01/09 modified by Makoto Matsumoto */ - this.mt[this.mti] >>>= 0; - /* for >32 bit machines */ - } - }; - - /* initialize by an array with array-length */ - /* init_key is the array for initializing keys */ - /* key_length is its length */ - /* slight change for C++, 2004/2/26 */ - MersenneTwister.prototype.init_by_array = function (init_key, key_length) { - var i = 1, j = 0, k, s; - this.init_genrand(19650218); - k = (this.N > key_length ? this.N : key_length); - for (; k; k--) { - s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30); - this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + ((s & 0x0000ffff) * 1664525))) + init_key[j] + j; /* non linear */ - this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */ - i++; - j++; - if (i >= this.N) { this.mt[0] = this.mt[this.N - 1]; i = 1; } - if (j >= key_length) { j = 0; } - } - for (k = this.N - 1; k; k--) { - s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30); - this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941)) - i; /* non linear */ - this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */ - i++; - if (i >= this.N) { this.mt[0] = this.mt[this.N - 1]; i = 1; } - } - - this.mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */ - }; - - /* generates a random number on [0,0xffffffff]-interval */ - MersenneTwister.prototype.genrand_int32 = function () { - var y; - var mag01 = new Array(0x0, this.MATRIX_A); - /* mag01[x] = x * MATRIX_A for x=0,1 */ - - if (this.mti >= this.N) { /* generate N words at one time */ - var kk; - - if (this.mti === this.N + 1) { /* if init_genrand() has not been called, */ - this.init_genrand(5489); /* a default initial seed is used */ - } - for (kk = 0; kk < this.N - this.M; kk++) { - y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk + 1]&this.LOWER_MASK); - this.mt[kk] = this.mt[kk + this.M] ^ (y >>> 1) ^ mag01[y & 0x1]; - } - for (;kk < this.N - 1; kk++) { - y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk + 1]&this.LOWER_MASK); - this.mt[kk] = this.mt[kk + (this.M - this.N)] ^ (y >>> 1) ^ mag01[y & 0x1]; - } - y = (this.mt[this.N - 1]&this.UPPER_MASK)|(this.mt[0]&this.LOWER_MASK); - this.mt[this.N - 1] = this.mt[this.M - 1] ^ (y >>> 1) ^ mag01[y & 0x1]; - - this.mti = 0; - } - - y = this.mt[this.mti++]; - - /* Tempering */ - y ^= (y >>> 11); - y ^= (y << 7) & 0x9d2c5680; - y ^= (y << 15) & 0xefc60000; - y ^= (y >>> 18); - - return y >>> 0; - }; - - /* generates a random number on [0,0x7fffffff]-interval */ - MersenneTwister.prototype.genrand_int31 = function () { - return (this.genrand_int32() >>> 1); - }; - - /* generates a random number on [0,1]-real-interval */ - MersenneTwister.prototype.genrand_real1 = function () { - return this.genrand_int32() * (1.0 / 4294967295.0); - /* divided by 2^32-1 */ - }; - - /* generates a random number on [0,1)-real-interval */ - MersenneTwister.prototype.random = function () { - return this.genrand_int32() * (1.0 / 4294967296.0); - /* divided by 2^32 */ - }; - - /* generates a random number on (0,1)-real-interval */ - MersenneTwister.prototype.genrand_real3 = function () { - return (this.genrand_int32() + 0.5) * (1.0 / 4294967296.0); - /* divided by 2^32 */ - }; - - /* generates a random number on [0,1) with 53-bit resolution*/ - MersenneTwister.prototype.genrand_res53 = function () { - var a = this.genrand_int32()>>>5, b = this.genrand_int32()>>>6; - return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0); - }; - - // BlueImp MD5 hashing algorithm from https://github.com/blueimp/JavaScript-MD5 - var BlueImpMD5 = function () {}; - - BlueImpMD5.prototype.VERSION = '1.0.1'; - - /* - * Add integers, wrapping at 2^32. This uses 16-bit operations internally - * to work around bugs in some JS interpreters. - */ - BlueImpMD5.prototype.safe_add = function safe_add(x, y) { - var lsw = (x & 0xFFFF) + (y & 0xFFFF), - msw = (x >> 16) + (y >> 16) + (lsw >> 16); - return (msw << 16) | (lsw & 0xFFFF); - }; - - /* - * Bitwise rotate a 32-bit number to the left. - */ - BlueImpMD5.prototype.bit_roll = function (num, cnt) { - return (num << cnt) | (num >>> (32 - cnt)); - }; - - /* - * These functions implement the five basic operations the algorithm uses. - */ - BlueImpMD5.prototype.md5_cmn = function (q, a, b, x, s, t) { - return this.safe_add(this.bit_roll(this.safe_add(this.safe_add(a, q), this.safe_add(x, t)), s), b); - }; - BlueImpMD5.prototype.md5_ff = function (a, b, c, d, x, s, t) { - return this.md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); - }; - BlueImpMD5.prototype.md5_gg = function (a, b, c, d, x, s, t) { - return this.md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); - }; - BlueImpMD5.prototype.md5_hh = function (a, b, c, d, x, s, t) { - return this.md5_cmn(b ^ c ^ d, a, b, x, s, t); - }; - BlueImpMD5.prototype.md5_ii = function (a, b, c, d, x, s, t) { - return this.md5_cmn(c ^ (b | (~d)), a, b, x, s, t); - }; - - /* - * Calculate the MD5 of an array of little-endian words, and a bit length. - */ - BlueImpMD5.prototype.binl_md5 = function (x, len) { - /* append padding */ - x[len >> 5] |= 0x80 << (len % 32); - x[(((len + 64) >>> 9) << 4) + 14] = len; - - var i, olda, oldb, oldc, oldd, - a = 1732584193, - b = -271733879, - c = -1732584194, - d = 271733878; - - for (i = 0; i < x.length; i += 16) { - olda = a; - oldb = b; - oldc = c; - oldd = d; - - a = this.md5_ff(a, b, c, d, x[i], 7, -680876936); - d = this.md5_ff(d, a, b, c, x[i + 1], 12, -389564586); - c = this.md5_ff(c, d, a, b, x[i + 2], 17, 606105819); - b = this.md5_ff(b, c, d, a, x[i + 3], 22, -1044525330); - a = this.md5_ff(a, b, c, d, x[i + 4], 7, -176418897); - d = this.md5_ff(d, a, b, c, x[i + 5], 12, 1200080426); - c = this.md5_ff(c, d, a, b, x[i + 6], 17, -1473231341); - b = this.md5_ff(b, c, d, a, x[i + 7], 22, -45705983); - a = this.md5_ff(a, b, c, d, x[i + 8], 7, 1770035416); - d = this.md5_ff(d, a, b, c, x[i + 9], 12, -1958414417); - c = this.md5_ff(c, d, a, b, x[i + 10], 17, -42063); - b = this.md5_ff(b, c, d, a, x[i + 11], 22, -1990404162); - a = this.md5_ff(a, b, c, d, x[i + 12], 7, 1804603682); - d = this.md5_ff(d, a, b, c, x[i + 13], 12, -40341101); - c = this.md5_ff(c, d, a, b, x[i + 14], 17, -1502002290); - b = this.md5_ff(b, c, d, a, x[i + 15], 22, 1236535329); - - a = this.md5_gg(a, b, c, d, x[i + 1], 5, -165796510); - d = this.md5_gg(d, a, b, c, x[i + 6], 9, -1069501632); - c = this.md5_gg(c, d, a, b, x[i + 11], 14, 643717713); - b = this.md5_gg(b, c, d, a, x[i], 20, -373897302); - a = this.md5_gg(a, b, c, d, x[i + 5], 5, -701558691); - d = this.md5_gg(d, a, b, c, x[i + 10], 9, 38016083); - c = this.md5_gg(c, d, a, b, x[i + 15], 14, -660478335); - b = this.md5_gg(b, c, d, a, x[i + 4], 20, -405537848); - a = this.md5_gg(a, b, c, d, x[i + 9], 5, 568446438); - d = this.md5_gg(d, a, b, c, x[i + 14], 9, -1019803690); - c = this.md5_gg(c, d, a, b, x[i + 3], 14, -187363961); - b = this.md5_gg(b, c, d, a, x[i + 8], 20, 1163531501); - a = this.md5_gg(a, b, c, d, x[i + 13], 5, -1444681467); - d = this.md5_gg(d, a, b, c, x[i + 2], 9, -51403784); - c = this.md5_gg(c, d, a, b, x[i + 7], 14, 1735328473); - b = this.md5_gg(b, c, d, a, x[i + 12], 20, -1926607734); - - a = this.md5_hh(a, b, c, d, x[i + 5], 4, -378558); - d = this.md5_hh(d, a, b, c, x[i + 8], 11, -2022574463); - c = this.md5_hh(c, d, a, b, x[i + 11], 16, 1839030562); - b = this.md5_hh(b, c, d, a, x[i + 14], 23, -35309556); - a = this.md5_hh(a, b, c, d, x[i + 1], 4, -1530992060); - d = this.md5_hh(d, a, b, c, x[i + 4], 11, 1272893353); - c = this.md5_hh(c, d, a, b, x[i + 7], 16, -155497632); - b = this.md5_hh(b, c, d, a, x[i + 10], 23, -1094730640); - a = this.md5_hh(a, b, c, d, x[i + 13], 4, 681279174); - d = this.md5_hh(d, a, b, c, x[i], 11, -358537222); - c = this.md5_hh(c, d, a, b, x[i + 3], 16, -722521979); - b = this.md5_hh(b, c, d, a, x[i + 6], 23, 76029189); - a = this.md5_hh(a, b, c, d, x[i + 9], 4, -640364487); - d = this.md5_hh(d, a, b, c, x[i + 12], 11, -421815835); - c = this.md5_hh(c, d, a, b, x[i + 15], 16, 530742520); - b = this.md5_hh(b, c, d, a, x[i + 2], 23, -995338651); - - a = this.md5_ii(a, b, c, d, x[i], 6, -198630844); - d = this.md5_ii(d, a, b, c, x[i + 7], 10, 1126891415); - c = this.md5_ii(c, d, a, b, x[i + 14], 15, -1416354905); - b = this.md5_ii(b, c, d, a, x[i + 5], 21, -57434055); - a = this.md5_ii(a, b, c, d, x[i + 12], 6, 1700485571); - d = this.md5_ii(d, a, b, c, x[i + 3], 10, -1894986606); - c = this.md5_ii(c, d, a, b, x[i + 10], 15, -1051523); - b = this.md5_ii(b, c, d, a, x[i + 1], 21, -2054922799); - a = this.md5_ii(a, b, c, d, x[i + 8], 6, 1873313359); - d = this.md5_ii(d, a, b, c, x[i + 15], 10, -30611744); - c = this.md5_ii(c, d, a, b, x[i + 6], 15, -1560198380); - b = this.md5_ii(b, c, d, a, x[i + 13], 21, 1309151649); - a = this.md5_ii(a, b, c, d, x[i + 4], 6, -145523070); - d = this.md5_ii(d, a, b, c, x[i + 11], 10, -1120210379); - c = this.md5_ii(c, d, a, b, x[i + 2], 15, 718787259); - b = this.md5_ii(b, c, d, a, x[i + 9], 21, -343485551); - - a = this.safe_add(a, olda); - b = this.safe_add(b, oldb); - c = this.safe_add(c, oldc); - d = this.safe_add(d, oldd); - } - return [a, b, c, d]; - }; - - /* - * Convert an array of little-endian words to a string - */ - BlueImpMD5.prototype.binl2rstr = function (input) { - var i, - output = ''; - for (i = 0; i < input.length * 32; i += 8) { - output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF); - } - return output; - }; - - /* - * Convert a raw string to an array of little-endian words - * Characters >255 have their high-byte silently ignored. - */ - BlueImpMD5.prototype.rstr2binl = function (input) { - var i, - output = []; - output[(input.length >> 2) - 1] = undefined; - for (i = 0; i < output.length; i += 1) { - output[i] = 0; - } - for (i = 0; i < input.length * 8; i += 8) { - output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32); - } - return output; - }; - - /* - * Calculate the MD5 of a raw string - */ - BlueImpMD5.prototype.rstr_md5 = function (s) { - return this.binl2rstr(this.binl_md5(this.rstr2binl(s), s.length * 8)); - }; - - /* - * Calculate the HMAC-MD5, of a key and some data (raw strings) - */ - BlueImpMD5.prototype.rstr_hmac_md5 = function (key, data) { - var i, - bkey = this.rstr2binl(key), - ipad = [], - opad = [], - hash; - ipad[15] = opad[15] = undefined; - if (bkey.length > 16) { - bkey = this.binl_md5(bkey, key.length * 8); - } - for (i = 0; i < 16; i += 1) { - ipad[i] = bkey[i] ^ 0x36363636; - opad[i] = bkey[i] ^ 0x5C5C5C5C; - } - hash = this.binl_md5(ipad.concat(this.rstr2binl(data)), 512 + data.length * 8); - return this.binl2rstr(this.binl_md5(opad.concat(hash), 512 + 128)); - }; - - /* - * Convert a raw string to a hex string - */ - BlueImpMD5.prototype.rstr2hex = function (input) { - var hex_tab = '0123456789abcdef', - output = '', - x, - i; - for (i = 0; i < input.length; i += 1) { - x = input.charCodeAt(i); - output += hex_tab.charAt((x >>> 4) & 0x0F) + - hex_tab.charAt(x & 0x0F); - } - return output; - }; - - /* - * Encode a string as utf-8 - */ - BlueImpMD5.prototype.str2rstr_utf8 = function (input) { - return unescape(encodeURIComponent(input)); - }; - - /* - * Take string arguments and return either raw or hex encoded strings - */ - BlueImpMD5.prototype.raw_md5 = function (s) { - return this.rstr_md5(this.str2rstr_utf8(s)); - }; - BlueImpMD5.prototype.hex_md5 = function (s) { - return this.rstr2hex(this.raw_md5(s)); - }; - BlueImpMD5.prototype.raw_hmac_md5 = function (k, d) { - return this.rstr_hmac_md5(this.str2rstr_utf8(k), this.str2rstr_utf8(d)); - }; - BlueImpMD5.prototype.hex_hmac_md5 = function (k, d) { - return this.rstr2hex(this.raw_hmac_md5(k, d)); - }; - - BlueImpMD5.prototype.md5 = function (string, key, raw) { - if (!key) { - if (!raw) { - return this.hex_md5(string); - } - - return this.raw_md5(string); - } - - if (!raw) { - return this.hex_hmac_md5(key, string); - } - - return this.raw_hmac_md5(key, string); - }; - - // CommonJS module - if (true) { - if ( true && module.exports) { - exports = module.exports = Chance; - } - exports.Chance = Chance; - } - - // Register as an anonymous AMD module - if (typeof define === 'function' && define.amd) { - define([], function () { - return Chance; - }); - } - - // if there is a importsScrips object define chance for worker - // allows worker to use full Chance functionality with seed - if (typeof importScripts !== 'undefined') { - chance = new Chance(); - self.Chance = Chance; - } - - // If there is a window object, that at least has a document property, - // instantiate and define chance on the window - if (typeof window === "object" && typeof window.document === "object") { - window.Chance = Chance; - window.chance = new Chance(); - } -})(); - - /***/ }), /***/ 77755: @@ -53220,12 +41515,14 @@ exports.isStaleBranchSyncComment = isStaleBranchSyncComment; exports.buildStaleBranchSyncComment = buildStaleBranchSyncComment; exports.buildAlignedBranchSyncComment = buildAlignedBranchSyncComment; const github_user_policy_1 = __nccwpck_require__(84403); -exports.BRANCH_SYNC_STALE_MARKER = ""; -exports.BRANCH_SYNC_ALIGNED_MARKER = ""; -const BRANCH_SYNC_KEY_MARKER = "'; +exports.BRANCH_SYNC_ALIGNED_MARKER = ''; +const BRANCH_SYNC_KEY_MARKER = ''; const SAFE_GITHUB_USERNAME = /^[A-Za-z0-9-]+$/u; @@ -54403,8 +42700,39 @@ function normalizeCopilotBotUsername(username) { : exports.DEFAULT_COPILOT_BOT_USERNAME; } /** Renders the stable command reference used by /copilot help. */ -function buildCopilotHelpMessage(username) { +function buildCopilotHelpMessage(username, locale = 'en-US') { const bot = normalizeCopilotBotUsername(username); + if ((0, locale_1.baseLanguage)(locale) === 'es') + return `## Comandos de Copilot + +Soy **@${bot}**, el asistente del repositorio. Usa estos comandos en una issue o pull request: + +### Solo lectura + +- \`/copilot help\` — muestra esta referencia. +- \`/copilot plan\` — propone un plan de implementación. +- \`/copilot clarify\` — identifica información pendiente y supuestos. +- \`/copilot estimate\` — estima el alcance y la complejidad. +- \`/copilot test-plan\` — propone una estrategia de pruebas. +- \`/copilot explain \` — explica código o comportamiento. +- \`/copilot diagnose\` — investiga un problema y sus posibles causas. +- \`/copilot analyze\` — analiza la issue, rama o pull request actual. +- \`/copilot review [effort=smart|low|default|high] [dry-run=true] [trace-rules=true] [suggested-changes=false]\` — ejecuta Bugbot con ajustes opcionales. +- \`/copilot findings\` — muestra los hallazgos potenciales. +- \`/copilot recheck\` — repite la revisión y reconcilia los hallazgos. +- \`/copilot description\` — actualiza la descripción del pull request. +- \`/copilot status\` — muestra el estado actual de la automatización. + +### Cambios + +- \`/copilot fix \` — corrige un hallazgo. +- \`/copilot fix all\` — corrige todos los hallazgos sin resolver. +- \`/copilot dismiss \` — descarta un hallazgo. +- \`/copilot remember \` — añade una regla de revisión autorizada y versionada. +- \`/copilot implement \` — aplica un cambio solicitado explícitamente. +- \`/copilot sync-branch [--dry-run] [--no-agent] [--from ]\` — integra la rama padre de forma segura. + +También puedes mencionar a **@${bot}** y escribir una pregunta. Los comandos que modifican archivos están limitados a mantenedores autorizados, ejecutan las comprobaciones configuradas e informan de los cambios resultantes.`; return `## Copilot commands I’m **@${bot}**, the repository assistant. Use these commands on an issue or pull request: @@ -54437,8 +42765,16 @@ I’m **@${bot}**, the repository assistant. Use these commands on an issue or p You can also ask a question in natural language by mentioning **@${bot}**. For example: “@${bot} update the issue's branch”. File-changing commands are restricted to authorized maintainers, run the configured checks, and report the resulting changes.`; } /** Renders the one-time onboarding comment for a newly created issue. */ -function buildCopilotWelcomeMessage(username) { +function buildCopilotWelcomeMessage(username, locale = 'en-US') { const bot = normalizeCopilotBotUsername(username); + if ((0, locale_1.baseLanguage)(locale) === 'es') + return `${exports.COPILOT_WELCOME_MARKER} + +Hola, soy **@${bot}**, el asistente de Copilot de este repositorio. + +Puedo responder preguntas, explicar el código, proponer planes de implementación y pruebas, revisar issues y pull requests y ayudar a los mantenedores autorizados a aplicar cambios. + +Usa \`/copilot help\` para ver los comandos disponibles o menciona a **@${bot}** con tu pregunta.`; return `${exports.COPILOT_WELCOME_MARKER} Hi! I’m **@${bot}**, the Copilot assistant for this repository. @@ -54455,6 +42791,7 @@ function buildCopilotWelcomeResult(username) { executed: true, stepFormat: 'markdown', steps: [buildCopilotWelcomeMessage(username)], + payload: Object.freeze({ publication: Object.freeze({ kind: 'welcome', botLogin: normalizeCopilotBotUsername(username) }) }), }); } @@ -54794,6 +43131,7 @@ exports.renderReconciliationPullRequest = renderReconciliationPullRequest; exports.renderDeploymentJobSummary = renderDeploymentJobSummary; exports.normalizeLocale = normalizeLocale; const managed_pull_request_1 = __nccwpck_require__(95914); +const publication_identity_policy_1 = __nccwpck_require__(45403); const EN = { release: "Release", hotfix: "Hotfix", currentStatus: "Current status", noAction: "No action is required while GitHub owns the pending transition.", actionRequired: "Action required", @@ -54861,6 +43199,15 @@ function renderDeploymentDashboard(operation, context) { const title = operation.kind === "release" ? messages.release : messages.hotfix; const action = deploymentAction(operation, messages); const lines = [ + (0, publication_identity_policy_1.buildPublicationMarker)({ + identity: { + topic: 'release', + target: { kind: 'issue', number: context.issue }, + key: `operation:${(0, publication_identity_policy_1.createSemanticDigest)(operation.operationId)}`, + }, + sourceVersion: `revision:${operation.revision}`, + digest: (0, publication_identity_policy_1.createSemanticDigest)({ locale: context.issueLocale, operation }), + }), deploymentDashboardMarker(operation.operationId, context.issue), "", `# ${operation.phase === "blocked" ? "❌" : operation.phase === "completed" ? "✅" : "🚀"} ${title} ${inline(operation.version)}`, "", `> **${messages.currentStatus}: ${messages.phase[operation.phase]}.**`, @@ -55357,6 +43704,155 @@ function progressPercentToColor(percent) { } +/***/ }), + +/***/ 45403: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.PUBLICATION_REPLY_MARKER_PREFIX = exports.PUBLICATION_DUPLICATE_MARKER_PREFIX = exports.PUBLICATION_MARKER_PREFIX = exports.PUBLICATION_SCHEMA = void 0; +exports.createSemanticDigest = createSemanticDigest; +exports.buildPublicationMarker = buildPublicationMarker; +exports.parsePublicationMarker = parsePublicationMarker; +exports.buildPublicationReplyMarker = buildPublicationReplyMarker; +exports.parsePublicationReplyMarker = parsePublicationReplyMarker; +exports.buildDuplicateMarker = buildDuplicateMarker; +const node_crypto_1 = __nccwpck_require__(6005); +const github_publication_1 = __nccwpck_require__(35793); +exports.PUBLICATION_SCHEMA = '1'; +exports.PUBLICATION_MARKER_PREFIX = 'copilot:publication'; +exports.PUBLICATION_DUPLICATE_MARKER_PREFIX = 'copilot:publication-duplicate'; +exports.PUBLICATION_REPLY_MARKER_PREFIX = 'copilot:reply'; +const SAFE_VALUE = /^[A-Za-z0-9._:-]{1,128}$/u; +const DIGEST = /^[a-f0-9]{8,64}$/u; +function createSemanticDigest(value) { + return (0, node_crypto_1.createHash)('sha256').update(stableSerialize(value), 'utf8').digest('hex').slice(0, 16); +} +function buildPublicationMarker(marker) { + const target = (0, github_publication_1.publicationTargetToken)(marker.identity.target); + for (const value of [marker.identity.topic, target, marker.identity.key, marker.sourceVersion]) { + if (!SAFE_VALUE.test(value)) + throw new Error('Publication marker contains an unsafe identity value.'); + } + if (!DIGEST.test(marker.digest)) + throw new Error('Publication marker contains an invalid digest.'); + return ``; +} +function parsePublicationMarker(body) { + if (typeof body !== 'string') + return undefined; + const match = body.match(//u); + if (!match) + return undefined; + const topic = match[1]; + if (!github_publication_1.PUBLICATION_TOPICS.includes(topic)) + return undefined; + const number = Number(match[3]); + if (!Number.isSafeInteger(number) || number < 1) + return undefined; + return Object.freeze({ + identity: Object.freeze({ + topic, + target: Object.freeze({ kind: match[2] === 'pr' ? 'pull-request' : 'issue', number }), + key: match[4], + }), + sourceVersion: match[5], + digest: match[6], + }); +} +function buildPublicationReplyMarker(marker) { + for (const value of [marker.target, marker.correlationId, marker.messageKey]) { + if (!SAFE_VALUE.test(value)) + throw new Error('Publication reply marker contains an unsafe identity value.'); + } + if (!DIGEST.test(marker.digest)) + throw new Error('Publication reply marker contains an invalid digest.'); + return ``; +} +function parsePublicationReplyMarker(body) { + if (typeof body !== 'string') + return undefined; + const match = body.match(//u); + if (!match) + return undefined; + return Object.freeze({ target: match[1], correlationId: match[2], messageKey: match[3], digest: match[4] }); +} +function buildDuplicateMarker(canonicalCommentId) { + if (!Number.isSafeInteger(canonicalCommentId) || canonicalCommentId < 1) { + throw new Error('Canonical comment id must be a positive integer.'); + } + return ``; +} +function stableSerialize(value) { + if (Array.isArray(value)) + return `[${value.map(stableSerialize).join(',')}]`; + if (value && typeof value === 'object') { + const record = value; + return `{${Object.keys(record).sort().map(key => `${JSON.stringify(key)}:${stableSerialize(record[key])}`).join(',')}}`; + } + return JSON.stringify(value) ?? 'null'; +} + + +/***/ }), + +/***/ 34223: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.SPANISH_PUBLICATION_CATALOG = exports.ENGLISH_PUBLICATION_CATALOG = void 0; +exports.resolveStaticPublicationCatalog = resolveStaticPublicationCatalog; +const locale_1 = __nccwpck_require__(15386); +const ENGLISH = Object.freeze({ + locale: 'en-US', + implementationPlan: 'Implementation plan', + planReady: 'Ready to start. No action is required from maintainers before implementation.', + planAcceptance: 'Acceptance', + commandsHint: 'Need something else? Mention the bot with a question or use `/copilot help`.', + progress: 'Progress', + progressState: Object.freeze({ 'not-started': 'not started', 'in-progress': 'in progress', complete: 'complete' }), + currentStatus: 'Current status', + next: 'Next', + noActionRequired: 'No action required.', + supersededStatus: 'This status was superseded by the canonical card.', + viewCurrentStatus: 'View current status', + duplicateReply: 'This duplicate response was suppressed.', + viewOriginalResponse: 'View the original response', +}); +const SPANISH = Object.freeze({ + locale: 'es-ES', + implementationPlan: 'Plan de implementación', + planReady: 'Listo para comenzar. No se requiere ninguna acción de mantenimiento antes de la implementación.', + planAcceptance: 'Aceptación', + commandsHint: '¿Necesitas algo más? Menciona al bot con una pregunta o usa `/copilot help`.', + progress: 'Progreso', + progressState: Object.freeze({ 'not-started': 'sin iniciar', 'in-progress': 'en curso', complete: 'completado' }), + currentStatus: 'Estado actual', + next: 'Siguiente paso', + noActionRequired: 'No se requiere ninguna acción.', + supersededStatus: 'Este estado fue sustituido por la tarjeta canónica.', + viewCurrentStatus: 'Ver estado actual', + duplicateReply: 'Esta respuesta duplicada se ha omitido.', + viewOriginalResponse: 'Ver la respuesta original', +}); +function resolveStaticPublicationCatalog(locale) { + const requestedLocale = (0, locale_1.canonicalizeLocaleTag)(locale || locale_1.DEFAULT_REPOSITORY_LOCALE); + if ((0, locale_1.baseLanguage)(requestedLocale) === 'es') { + return Object.freeze({ requestedLocale, catalog: SPANISH, fallback: false }); + } + if ((0, locale_1.baseLanguage)(requestedLocale) === 'en') { + return Object.freeze({ requestedLocale, catalog: ENGLISH, fallback: false }); + } + return Object.freeze({ requestedLocale, catalog: ENGLISH, fallback: true }); +} +exports.ENGLISH_PUBLICATION_CATALOG = ENGLISH; +exports.SPANISH_PUBLICATION_CATALOG = SPANISH; + + /***/ }), /***/ 39410: @@ -56705,6 +45201,7 @@ exports.buildCopilotStatusResult = buildCopilotStatusResult; exports.formatCopilotStatus = formatCopilotStatus; const result_1 = __nccwpck_require__(73817); const bugbot_result_finding_state_projection_policy_1 = __nccwpck_require__(98117); +const locale_1 = __nccwpck_require__(15386); /** Builds a read-only status snapshot from the facts already loaded by setup. */ function buildCopilotStatusSnapshot(execution) { const issueLabels = [...(execution.labels?.currentIssueLabels ?? [])]; @@ -56767,7 +45264,9 @@ function buildCopilotStatusResult(snapshot, taskId) { payload: { status: snapshot }, }); } -function formatCopilotStatus(snapshot) { +function formatCopilotStatus(snapshot, locale = 'en-US') { + if ((0, locale_1.baseLanguage)(locale) === 'es') + return formatSpanishCopilotStatus(snapshot); const lines = [ '## Copilot status', `- **Repository:** ${snapshot.owner}/${snapshot.repository}`, @@ -56788,6 +45287,27 @@ function formatCopilotStatus(snapshot) { } return lines.join('\n'); } +function formatSpanishCopilotStatus(snapshot) { + const lines = [ + '## Estado de Copilot', + `- **Repositorio:** ${snapshot.owner}/${snapshot.repository}`, + `- **Destino:** ${snapshot.target}${snapshot.issueNumber ? ` #${snapshot.issueNumber}` : ''}${snapshot.pullRequestNumber ? ` / PR #${snapshot.pullRequestNumber}` : ''}`, + `- **Evento:** ${snapshot.event}${snapshot.action ? ` (${snapshot.action})` : ''}`, + `- **Rama:** ${snapshot.branch ?? 'desconocida'}`, + `- **Ciclo de vida:** ${snapshot.lifecycle ?? 'sin definir'}`, + `- **Esperando a:** ${snapshot.waitingFor ?? 'sin respuesta humana pendiente'}`, + `- **Política de descripción de PR:** ${snapshot.pullRequestDescriptionMode}`, + `- **Etiquetas de issue:** ${snapshot.issueLabels.length > 0 ? snapshot.issueLabels.join(', ') : 'ninguna'}`, + `- **Etiquetas de PR:** ${snapshot.pullRequestLabels.length > 0 ? snapshot.pullRequestLabels.join(', ') : 'ninguna'}`, + ]; + if (snapshot.findingStateEvidence === 'invalid') { + lines.push('- **Hallazgos de Bugbot:** evidencia no válida; revisa el resultado del workflow.'); + } + else if (snapshot.findingStates) { + lines.push(`- **Hallazgos de Bugbot:** ${snapshot.findingStates.open} abiertos, ${snapshot.findingStates.reopened} reabiertos, ${snapshot.findingStates.verificationRequired} requieren verificación, ${snapshot.findingStates.unknown} desconocidos, ${snapshot.findingStates.resolved} resueltos`); + } + return lines.join('\n'); +} /***/ }), @@ -57471,8 +45991,9 @@ const result_1 = __nccwpck_require__(73817); const issue_inactivity_1 = __nccwpck_require__(38572); const logging_ports_1 = __nccwpck_require__(6152); const application_error_1 = __nccwpck_require__(75999); +const locale_1 = __nccwpck_require__(15386); +const publication_identity_policy_1 = __nccwpck_require__(45403); const TASK_ID = 'CloseInactiveIssuesUseCase'; -const INACTIVITY_COMMENT = (thresholdHours) => `This issue was automatically closed due to inactivity while waiting for a response. No activity was detected for at least **${thresholdHours} hours**. Reopen it and add a comment if it still needs attention.`; /** Scans waiting issues and closes only candidates that remain inactive. */ async function runCloseInactiveIssuesWorkflow(param, dependencies) { const waitingLabels = unique([ @@ -57521,7 +46042,7 @@ async function runCloseInactiveIssuesWorkflow(param, dependencies) { continue; } closedCount++; - await dependencies.issueClosurePort.addComment(candidate.number, INACTIVITY_COMMENT(thresholdHours)); + await dependencies.issueClosurePort.addComment(candidate.number, buildInactivityExplanation(candidate, thresholdHours, param.locale)); (0, logging_ports_1.logInfo)(`Issue #${candidate.number} closed after inactivity.`); } catch (error) { @@ -57557,6 +46078,34 @@ async function runCloseInactiveIssuesWorkflow(param, dependencies) { })]; } } +function buildInactivityExplanation(candidate, thresholdHours, locale) { + const digest = (0, publication_identity_policy_1.createSemanticDigest)({ updatedAt: candidate.updatedAt, thresholdHours }); + const marker = (0, publication_identity_policy_1.buildPublicationMarker)({ + identity: { topic: 'inactivity', target: { kind: 'issue', number: candidate.number }, key: 'closure' }, + sourceVersion: `policy:${digest}`, + digest, + }); + if ((0, locale_1.baseLanguage)(locale) === 'es') { + return [ + marker, + '', + '## Issue cerrada por inactividad', + '', + `No se detectó actividad durante al menos **${thresholdHours} horas** mientras esta issue esperaba una respuesta.`, + '', + 'Si todavía necesita atención, vuelve a abrirla y añade un comentario con el contexto actualizado.', + ].join('\n'); + } + return [ + marker, + '', + '## Issue closed after inactivity', + '', + `No activity was detected for at least **${thresholdHours} hours** while this issue was waiting for a response.`, + '', + 'If it still needs attention, reopen it and add a comment with the current context.', + ].join('\n'); +} async function listCandidates(_param, waitingLabels, queryPort) { const candidates = []; for (const label of waitingLabels) { @@ -58856,6 +47405,7 @@ class ObserveBranchSyncUseCase { repository: context.repository.name, dependency, comparison, + locale: context.locale, }); if (latest && (0, branch_sync_notification_policy_1.isStaleBranchSyncComment)(latest.body)) { await this.notifications.updateComment(dependency.issueNumber, latest.id, comment); @@ -58866,7 +47416,7 @@ class ObserveBranchSyncUseCase { return success(dependency, comparison.behindBy, "stale"); } if (latest && (0, branch_sync_notification_policy_1.isStaleBranchSyncComment)(latest.body)) { - await this.notifications.updateComment(dependency.issueNumber, latest.id, (0, branch_sync_notification_policy_1.buildAlignedBranchSyncComment)(dependency)); + await this.notifications.updateComment(dependency.issueNumber, latest.id, (0, branch_sync_notification_policy_1.buildAlignedBranchSyncComment)(dependency, context.locale)); } return success(dependency, 0, "aligned"); } @@ -60129,6 +48679,7 @@ function runHelpCommand(param, options) { executed: true, stepFormat: 'markdown', steps: [(0, copilot_interaction_policy_1.buildCopilotHelpMessage)(param.trustedBotLogin)], + payload: Object.freeze({ publication: Object.freeze({ kind: 'help', botLogin: param.trustedBotLogin }) }), })]; } async function runDescriptionCommand(param, options, actorAuthorizationPort) { @@ -61718,6 +50269,7 @@ function projectInactivityContext(source) { ]), activityLabel: source.labels.lifecycle.aiProcessing, thresholdHours: source.inactivityThresholdHours, + locale: source.locale?.issue ?? 'en-US', }); } function projectBranchObservationContext(source) { @@ -61726,6 +50278,7 @@ function projectBranchObservationContext(source) { deletedPush: typeof source.inputs?.after === 'string' && /^0+$/u.test(source.inputs.after), ...(source.tokenUser ? { trustedBotLogin: source.tokenUser } : {}), repository: Object.freeze({ owner: source.owner, name: source.repo }), + locale: source.locale?.issue ?? 'en-US', }); } function projectUserRequestContext(source) { @@ -65499,7 +54052,6 @@ function isRecord(value) { */ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.publishFindings = publishFindings; -const comment_watermark_1 = __nccwpck_require__(23623); const finding_1 = __nccwpck_require__(31011); const publish_issue_finding_comment_1 = __nccwpck_require__(84950); const publish_pr_review_comments_1 = __nccwpck_require__(50352); @@ -65507,16 +54059,12 @@ const publish_overflow_comment_1 = __nccwpck_require__(10974); async function publishFindings(param) { const { operation, context, findings, commitSha, overflowCount = 0, overflowTitles = [], ports } = param; const { existingByFindingId, canonicalPullRequest, prContext } = context; - const watermark = commitSha - ? (0, comment_watermark_1.getCommentWatermark)({ commitSha, owner: operation.repository.owner, repo: operation.repository.name }) - : (0, comment_watermark_1.getCommentWatermark)(); const reviewPublisher = prContext && canonicalPullRequest ? new publish_pr_review_comments_1.PullRequestReviewCommentPublisher({ repository: ports.pullRequestComments, operation, openPrNumber: canonicalPullRequest.number, prContext, - watermark, ruleSources: context.reviewRuleSources, omittedRuleCount: context.omittedReviewRules, }) @@ -65618,7 +54166,7 @@ class PullRequestReviewCommentPublisher { } // Existing comments do not carry enough anchor metadata to prove that a // GitHub suggestion is still attached to a RIGHT-side changed line. - const body = `${(0, bugbot_finding_marker_policy_1.buildCommentBody)(finding, false, undefined, { includeSuggestedChange: false })}\n\n${this.options.watermark}`; + const body = (0, bugbot_finding_marker_policy_1.buildCommentBody)(finding, false, undefined, { includeSuggestedChange: false }); await this.options.repository.updatePullRequestReviewComment(existing.pullRequest.commentIdentity, body); if (existing.pullRequest.resolved || existing.pullRequest.threadResolved === true) { // Persist the open marker before reopening the native thread. This @@ -65632,7 +54180,7 @@ class PullRequestReviewCommentPublisher { const findingBody = (0, bugbot_finding_marker_policy_1.buildCommentBody)(finding, false, undefined, { includeSuggestedChange: allowSuggestedChanges && anchor?.subjectType === 'line' && anchor.side === 'RIGHT', }); - const body = `${findingBody}\n\n${this.options.watermark}`; + const body = findingBody; this.findingsToCreate.push(finding); if (!anchor) { this.unanchoredBodies.push(findingBody); @@ -65659,7 +54207,7 @@ class PullRequestReviewCommentPublisher { if (this.findingsToCreate.length === 0 && overflowCount === 0) return; const { repository, operation, openPrNumber, prContext } = this.options; - await repository.createReviewWithComments(openPrNumber, prContext.prHeadSha, buildReviewSummary(this.findingsToCreate, this.commentsToCreate.length, this.unanchoredBodies, overflowCount, overflowTitles, this.options.watermark, operation.analysis.reviewConfiguration.traceRules + await repository.createReviewWithComments(openPrNumber, prContext.prHeadSha, buildReviewSummary(this.findingsToCreate, this.commentsToCreate.length, this.unanchoredBodies, overflowCount, overflowTitles, operation.analysis.reviewConfiguration.traceRules ? this.options.ruleSources ?? [] : [], operation.analysis.reviewConfiguration.traceRules ? this.options.omittedRuleCount ?? 0 @@ -65698,7 +54246,7 @@ function resolveReviewAnchor(reportedLine, reportedEndLine, reportedPath, contex const fallback = context.prFiles.find((file) => file.status !== 'removed') ?? context.prFiles[0]; return fallback ? { path: fallback.filename, subjectType: 'file' } : undefined; } -function buildReviewSummary(findings, inlineCount, unanchoredBodies, overflowCount, overflowTitles, watermark, ruleSources = [], omittedRuleCount = 0, analyzedHeadSha = 'unknown', locale = 'en-US') { +function buildReviewSummary(findings, inlineCount, unanchoredBodies, overflowCount, overflowTitles, ruleSources = [], omittedRuleCount = 0, analyzedHeadSha = 'unknown', locale = 'en-US') { const findingLines = findings.map((finding) => { const severity = sanitizeSummaryText(finding.severity, 32) || "unspecified"; const title = sanitizeSummaryText(finding.title, 500) || 'Potential problem'; @@ -65734,7 +54282,6 @@ function buildReviewSummary(findings, inlineCount, unanchoredBodies, overflowCou rows.push(`| — | ${omittedRuleCount} omitted by duplicate, empty, or combined-budget policy |`); sections.push(`### Review configuration\n\nRules in effective precedence order:\n\n| Source | Status |\n| --- | --- |\n${rows.join('\n')}`); } - sections.push(watermark); return sections.join("\n\n"); } function sanitizeSummaryText(value, maximum) { @@ -66168,6 +54715,8 @@ exports.synchronizeBugbotReviewPresentation = synchronizeBugbotReviewPresentatio const bugbot_review_presentation_policy_1 = __nccwpck_require__(43799); const bugbot_review_ownership_policy_1 = __nccwpck_require__(83288); const review_projection_1 = __nccwpck_require__(80859); +const publication_identity_policy_1 = __nccwpck_require__(45403); +const publication_message_catalog_1 = __nccwpck_require__(34223); const MAX_REVIEW_UPDATES_PER_RUN = 20; const REVIEW_UPDATE_CONCURRENCY = 4; /** @@ -66250,10 +54799,13 @@ async function synchronizeStatusCard(input, projection, navigation) { failed = true; } const duplicateResults = await mapWithConcurrency(trustedStatusComments.slice(1), REVIEW_UPDATE_CONCURRENCY, async (duplicate) => { + const messages = (0, publication_message_catalog_1.resolveStaticPublicationCatalog)(input.target.locale).catalog; await input.ports.comments.updateComment(input.target.pullRequestNumber, duplicate.id, [ - '## 🤖 Bugbot status moved', + (0, publication_identity_policy_1.buildDuplicateMarker)(canonical?.id ?? duplicate.id), '', - `This duplicate status card is no longer current. [Use the canonical PR status](${navigation.pullRequestUrl}).`, + messages.supersededStatus, + '', + `[${messages.viewCurrentStatus}](${navigation.pullRequestUrl}).`, ].join('\n'), { commitSha: input.snapshot.verifiedHeadSha }); }); if (duplicateResults.includes('rejected')) @@ -66616,81 +55168,6 @@ function logSize(size, githubSize, reason, currentLabel) { } -/***/ }), - -/***/ 90762: -/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.buildCommitNotificationContent = buildCommitNotificationContent; -const list_utils_1 = __nccwpck_require__(42277); -const SEPARATOR = "------------------------------------------------------"; -function buildCommitNotificationContent(param, commitPrefix) { - const theme = resolveTheme(param); - let body = ` -# ${theme.title} - -**Changes on branch \`${param.branch}\`:** - -`; - let shouldWarn = false; - for (const commit of param.commits) { - const commitMessage = commit.message ?? ""; - body += ` -${SEPARATOR} - -- ${commit.id ?? "unknown"} by **${commit.author?.name ?? "unknown"}** (@${commit.author?.username ?? "unknown"}) -\`\`\` -${commitMessage.split(`${commitPrefix}: `).join("")} -\`\`\` - -`; - if (hasUnexpectedPrefix(commitMessage, commitPrefix)) - shouldWarn = true; - } - if (shouldWarn && commitPrefix.length > 0) { - body += ` -${SEPARATOR} -## ⚠️ Attention - -One or more commits didn't start with the prefix **${commitPrefix}**. - -\`\`\` -${commitPrefix}: created hello-world app -\`\`\` -`; - } - if (theme.image && param.imagesOnCommit) { - body += ` -${SEPARATOR} - -![image](${theme.image}) -`; - } - return { body, shouldWarn }; -} -function resolveTheme(param) { - const titles = { - release: '🚀 Release News', - hotfix: '🔥🐛 Hotfix News', - bugfix: '🐛 Bugfix News', - feature: '✨ Feature News', - docs: '📝 Documentation News', - chore: '🔧 Chore News', - automatic: '🪄 Automatic News', - }; - return { title: titles[param.theme], image: (0, list_utils_1.getRandomElement)([...param.themeImages]) }; -} -function hasUnexpectedPrefix(commitMessage, commitPrefix) { - return commitPrefix.length > 0 - && !commitMessage.startsWith(commitPrefix) - && !commitMessage.startsWith("Merge branch ") - && !commitMessage.startsWith("gh-action: "); -} - - /***/ }), /***/ 6287: @@ -67131,35 +55608,25 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.runNotifyNewCommitOnIssueWorkflow = runNotifyNewCommitOnIssueWorkflow; const result_1 = __nccwpck_require__(73817); const logging_ports_1 = __nccwpck_require__(6152); -const execute_script_use_case_1 = __nccwpck_require__(65440); -const commit_notification_content_policy_1 = __nccwpck_require__(90762); const application_error_1 = __nccwpck_require__(75999); async function runNotifyNewCommitOnIssueWorkflow(param, taskId, issueRepository) { const result = []; try { - const branchName = param.branch; - let commitPrefix = ""; - if (param.commitPrefixBuilder.length > 0) { - commitPrefix = (0, execute_script_use_case_1.buildCommitPrefix)(branchName, param.commitPrefixBuilder); - (0, logging_ports_1.logDebugInfo)(`Commit prefix: ${commitPrefix}`); - } - const { body } = (0, commit_notification_content_policy_1.buildCommitNotificationContent)(param, commitPrefix); if (param.reopenOnPush) { const opened = await issueRepository.openIssue(param.issueNumber); - if (opened) { - await issueRepository.addComment(param.issueNumber, `This issue was re-opened after pushing new commits to the branch \`${branchName}\`.`); - } + (0, logging_ports_1.logInfo)(opened + ? `Issue #${param.issueNumber} reopened after a push; native state is sufficient.` + : `Issue #${param.issueNumber} was already open; no conversation notification was created.`); } - await issueRepository.addComment(param.issueNumber, body); } catch (error) { - const semanticError = (0, application_error_1.toApplicationError)(error, 'provider.unavailable', 'Unable to notify the issue about the new commit.'); + const semanticError = (0, application_error_1.toApplicationError)(error, 'provider.unavailable', 'Unable to synchronize the issue state after the new commit.'); (0, logging_ports_1.logError)(semanticError); result.push(new result_1.Result({ id: taskId, success: false, executed: true, - steps: ["Tried to notify the new commit on the issue, but there was a problem."], + steps: ["Tried to synchronize issue state after the new commit, but there was a problem."], errors: [semanticError], })); } @@ -68920,7 +57387,6 @@ class CloseIssueAfterMergingUseCase { const closed = await this.issueRepository.closeIssue(param.issueNumber); if (closed) { (0, logging_ports_1.logInfo)(`Issue #${param.issueNumber} closed after merging PR #${param.pullRequestNumber}.`); - await this.issueRepository.addComment(param.issueNumber, `This issue was closed after merging #${param.pullRequestNumber}.`); result.push(new result_1.Result({ id: this.taskId, success: true, @@ -68982,15 +57448,13 @@ class CloseNotAllowedIssueUseCase { try { const closed = await this.issueRepository.closeIssue(param.issueNumber); if (closed) { - (0, logging_ports_1.logInfo)(`Issue #${param.issueNumber} closed (author not allowed). Adding comment.`); - await this.issueRepository.addComment(param.issueNumber, `This issue has been closed because the author is not a member of the project. The user may be banned if the fact is repeated.`); + (0, logging_ports_1.logInfo)(`Issue #${param.issueNumber} closed (author not allowed).`); result.push(new result_1.Result({ id: this.taskId, success: true, executed: true, - steps: [ - `#${param.issueNumber} was automatically closed because the author is not a member of the project.` - ] + steps: [`#${param.issueNumber} was automatically closed because the author is not a member of the project.`], + payload: Object.freeze({ publication: Object.freeze({ kind: 'access-policy' }) }), })); } else { @@ -77919,7 +66383,6 @@ exports.IssueClosureRepository = IssueClosureRepository; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.IssueContentRepository = void 0; -const comment_watermark_1 = __nccwpck_require__(23623); const comment_content_policy_1 = __nccwpck_require__(77454); const logger_1 = __nccwpck_require__(91151); const github_pagination_policy_1 = __nccwpck_require__(44812); @@ -77974,13 +66437,13 @@ class IssueContentRepository { (0, logger_1.logDebugInfo)(`Skipped empty comment publication for Issue ${issueNumber}.`); return; } - const watermark = (0, comment_watermark_1.getCommentWatermark)(options?.commitSha ? { commitSha: options.commitSha, owner, repo: repository } : undefined); + void options; const octokit = this.githubClient.getClient(token); await octokit.rest.issues.createComment({ owner, repo: repository, issue_number: issueNumber, - body: `${comment}\n\n${watermark}`, + body: comment, }); (0, logger_1.logDebugInfo)(`Comment added to Issue ${issueNumber}.`); }; @@ -77989,13 +66452,13 @@ class IssueContentRepository { (0, logger_1.logDebugInfo)(`Skipped empty comment update for Issue ${issueNumber}.`); return; } - const watermark = (0, comment_watermark_1.getCommentWatermark)(options?.commitSha ? { commitSha: options.commitSha, owner, repo: repository } : undefined); + void options; const octokit = this.githubClient.getClient(token); await octokit.rest.issues.updateComment({ owner, repo: repository, comment_id: commentId, - body: `${comment}\n\n${watermark}`, + body: comment, }); (0, logger_1.logDebugInfo)(`Comment ${commentId} updated in Issue ${issueNumber}.`); }; @@ -82923,6 +71386,37 @@ function isPullRequestConversationComment(input) { } +/***/ }), + +/***/ 35793: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.PUBLICATION_TOPICS = void 0; +exports.publicationIdentityEquals = publicationIdentityEquals; +exports.publicationTargetToken = publicationTargetToken; +exports.PUBLICATION_TOPICS = [ + 'plan', + 'progress', + 'branch-sync', + 'bugbot', + 'release', + 'inactivity', + 'access-policy', +]; +function publicationIdentityEquals(left, right) { + return left.topic === right.topic + && left.target.kind === right.target.kind + && left.target.number === right.target.number + && left.key === right.key; +} +function publicationTargetToken(target) { + return `${target.kind === 'pull-request' ? 'pr' : 'issue'}:${target.number}`; +} + + /***/ }), /***/ 84403: @@ -84826,14 +73320,13 @@ function createIssueUseCaseCompositionRoot(binding) { const projectContent = (0, shared_capability_port_binding_1.bindProjectContent)(issueMetadata, projectBoard.command, projectBoard.link, binding); const boundProjectBoard = (0, lifecycle_capability_port_binding_1.bindProjectBoardCommands)(projectBoard.command, binding); const boundBranchLifecycle = (0, lifecycle_capability_port_binding_1.bindBranchLifecycle)(branchLifecycle, binding); - const boundIssueClosure = (0, lifecycle_capability_port_binding_1.bindIssueClosure)(issueClosure, binding); const boundIssueAssignee = (0, lifecycle_capability_port_binding_1.bindIssueAssignee)(issueAssignee, binding); const boundOrganizationMembers = (0, lifecycle_capability_port_binding_1.bindOrganizationMemberSelection)(organizationMembers, binding); const boundLinkedBranch = (0, lifecycle_capability_port_binding_1.bindLinkedBranchCommand)(linkedBranch, binding); const moveIssueToInProgress = new move_issue_to_in_progress_1.MoveIssueToInProgressUseCase(boundProjectBoard); const workflowSteps = { checkPermissions: new check_permissions_use_case_1.CheckPermissionsUseCase((0, shared_capability_port_binding_1.bindOrganizationMembers)(organizationMembers, binding)), - closeNotAllowedIssue: new close_not_allowed_issue_use_case_1.CloseNotAllowedIssueUseCase(boundIssueClosure), + closeNotAllowedIssue: new close_not_allowed_issue_use_case_1.CloseNotAllowedIssueUseCase((0, lifecycle_capability_port_binding_1.bindIssueState)(issueClosure, binding)), removeIssueBranches: new remove_issue_branches_use_case_1.RemoveIssueBranchesUseCase(boundBranchLifecycle), assignMemberToIssue: new assign_members_to_issue_use_case_1.AssignMemberToIssueUseCase(boundIssueAssignee, boundOrganizationMembers), updateTitle: new update_title_use_case_1.UpdateTitleUseCase((0, shared_capability_port_binding_1.bindIssueTitle)(issueTitle, binding)), @@ -84861,6 +73354,7 @@ exports.bindIssueAssignee = bindIssueAssignee; exports.bindOrganizationMemberSelection = bindOrganizationMemberSelection; exports.bindPullRequestReviewer = bindPullRequestReviewer; exports.bindIssueClosure = bindIssueClosure; +exports.bindIssueState = bindIssueState; exports.bindIssueTypeAssignment = bindIssueTypeAssignment; exports.bindProjectBoardCommands = bindProjectBoardCommands; exports.bindBranchLifecycle = bindBranchLifecycle; @@ -84900,6 +73394,11 @@ function bindIssueClosure(port, binding) { addComment: (issueNumber, comment) => port.addComment(binding.owner, binding.repository, issueNumber, comment, binding.token), }); } +function bindIssueState(port, binding) { + return Object.freeze({ + closeIssue: (issueNumber) => port.closeIssue(binding.owner, binding.repository, issueNumber, binding.token), + }); +} function bindIssueTypeAssignment(port, binding) { return Object.freeze({ setIssueType: (issueNumber, issueType) => port.setIssueType(binding.owner, binding.repository, issueNumber, issueType, binding.token), @@ -85125,7 +73624,7 @@ function createPullRequestReviewCommentUseCaseCompositionRoot(binding) { return new pull_request_review_comment_use_case_1.PullRequestReviewCommentUseCase(new check_pull_request_comment_language_use_case_1.CheckPullRequestCommentLanguageUseCase(new comment_language_translation_workflow_1.CommentLanguageTranslationWorkflow(language)), new detect_bugbot_fix_intent_use_case_1.DetectBugbotFixIntentUseCase(findings, bugbot.scm.context), new think_use_case_1.ThinkUseCase((0, shared_capability_port_binding_1.bindIssueDescriptionQuery)((0, issue_content_composition_root_1.createIssueContentCompositionRoot)(), binding), (0, shared_capability_port_binding_1.bindIssueNotification)((0, issue_interaction_composition_root_1.createIssueNotificationRepository)(), binding), findings), new bugbot_autofix_use_case_1.BugbotAutofixUseCase(fixer, bugbot.scm.context, bugbotGit), new user_request_use_case_1.DoUserRequestUseCase(fixer, bugbotGit), (0, actor_authorization_composition_root_1.createActorAuthorizationRepository)(), bugbotGit, new dismiss_bugbot_findings_use_case_1.DismissBugbotFindingsUseCase({ contextPorts: bugbot.scm.context, resolutionPorts: bugbot.scm.resolution }), new detect_potential_problems_use_case_1.DetectPotentialProblemsUseCase(findings, bugbot.scm, bugbot.telemetry), pullRequestDescription, new remember_bugbot_rule_use_case_1.RememberBugbotRuleUseCase(bugbot.rules), branchSync); } function createCommitUseCaseCompositionRoot(projectBoardCommandPort, binding) { - return new commit_use_case_1.CommitUseCase(new notify_new_commit_on_issue_use_case_1.NotifyNewCommitOnIssueUseCase((0, push_single_action_capability_port_binding_1.bindIssuePushNotification)((0, issue_interaction_composition_root_1.createIssueNotificationRepository)(), binding)), new check_changes_issue_size_use_case_1.CheckChangesIssueSizeUseCase((0, lifecycle_capability_port_binding_2.bindProjectBoardCommands)(projectBoardCommandPort, binding), (0, lifecycle_capability_port_binding_2.bindIssueLabels)((0, issue_labels_composition_root_1.createIssueLabelRepository)(), binding), (0, push_single_action_capability_port_binding_1.bindPullRequestBranchQuery)(new pull_request_lifecycle_repository_1.PullRequestLifecycleRepository((0, github_pull_request_client_factory_1.createPullRequestLifecycleClient)()), binding), (0, push_single_action_capability_port_binding_1.bindBranchChangeSize)(new branch_compare_repository_1.BranchCompareRepository((0, github_branch_client_factory_1.createBranchComparisonClient)()), binding)), createDetectPotentialProblemsUseCase(binding), (0, check_progress_composition_root_1.createCheckProgressCompositionRoot)(binding), (0, actor_authorization_composition_root_1.createActorAuthorizationRepository)()); + return new commit_use_case_1.CommitUseCase(new notify_new_commit_on_issue_use_case_1.NotifyNewCommitOnIssueUseCase((0, push_single_action_capability_port_binding_1.bindIssueReopen)((0, issue_interaction_composition_root_1.createIssueNotificationRepository)(), binding)), new check_changes_issue_size_use_case_1.CheckChangesIssueSizeUseCase((0, lifecycle_capability_port_binding_2.bindProjectBoardCommands)(projectBoardCommandPort, binding), (0, lifecycle_capability_port_binding_2.bindIssueLabels)((0, issue_labels_composition_root_1.createIssueLabelRepository)(), binding), (0, push_single_action_capability_port_binding_1.bindPullRequestBranchQuery)(new pull_request_lifecycle_repository_1.PullRequestLifecycleRepository((0, github_pull_request_client_factory_1.createPullRequestLifecycleClient)()), binding), (0, push_single_action_capability_port_binding_1.bindBranchChangeSize)(new branch_compare_repository_1.BranchCompareRepository((0, github_branch_client_factory_1.createBranchComparisonClient)()), binding)), createDetectPotentialProblemsUseCase(binding), (0, check_progress_composition_root_1.createCheckProgressCompositionRoot)(binding), (0, actor_authorization_composition_root_1.createActorAuthorizationRepository)()); } function createMainRunRouteCompositionRoot(projectBoardCommandPort, surface) { let singleAction; @@ -85334,7 +73833,7 @@ exports.bindRepositoryTag = bindRepositoryTag; exports.bindRepositoryRelease = bindRepositoryRelease; exports.bindRepositoryDefaultBranch = bindRepositoryDefaultBranch; exports.bindIssueCommentPublication = bindIssueCommentPublication; -exports.bindIssuePushNotification = bindIssuePushNotification; +exports.bindIssueReopen = bindIssueReopen; exports.bindBranchListQuery = bindBranchListQuery; exports.bindPullRequestBranchQuery = bindPullRequestBranchQuery; exports.bindIssueProgress = bindIssueProgress; @@ -85443,10 +73942,9 @@ function bindIssueCommentPublication(port, binding) { listIssueComments: (issueNumber) => port.listIssueComments(binding.owner, binding.repository, issueNumber, binding.token), }); } -function bindIssuePushNotification(port, binding) { +function bindIssueReopen(port, binding) { return Object.freeze({ openIssue: (issueNumber) => port.openIssue(binding.owner, binding.repository, issueNumber, binding.token), - addComment: (issueNumber, comment) => port.addComment(binding.owner, binding.repository, issueNumber, comment, binding.token), }); } function bindBranchListQuery(port, binding) { @@ -88368,28 +76866,10 @@ function format(value) { "use strict"; -/** - * Watermark appended to comments (issues and PRs) to attribute Copilot. - * Bugbot comments include commit link and note about auto-update on new commits. - */ Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.COPILOT_MARKETPLACE_URL = void 0; -exports.getCommentWatermark = getCommentWatermark; exports.stripTrailingCommentWatermarks = stripTrailingCommentWatermarks; -exports.COPILOT_MARKETPLACE_URL = 'https://github.com/marketplace/actions/copilot-github-with-super-powers'; -const DEFAULT_WATERMARK = `Made with ❤️ by [vypdev/copilot](${exports.COPILOT_MARKETPLACE_URL})`; -function commitUrl(owner, repo, sha) { - return `https://github.com/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commit/${sha}`; -} -function getCommentWatermark(options) { - if (options?.commitSha && options?.owner && options?.repo) { - const url = commitUrl(options.owner, options.repo, options.commitSha); - return `Written by [vypdev/copilot](${exports.COPILOT_MARKETPLACE_URL}) for commit [${options.commitSha}](${url}). This will update automatically on new commits.`; - } - return DEFAULT_WATERMARK; -} const TRAILING_COMMENT_WATERMARK = /\s*(?:Made with ❤️ by|Written by) \[vypdev\/copilot\]\(https:\/\/github\.com\/marketplace\/actions\/copilot-github-with-super-powers\)[^<]*<\/sup>\s*$/u; -/** Removes all trailing Copilot watermarks before a read-modify-write update. */ +/** Removes legacy trailing Copilot watermarks before a read-modify-write update. */ function stripTrailingCommentWatermarks(comment) { let stripped = comment; while (TRAILING_COMMENT_WATERMARK.test(stripped)) { @@ -88451,36 +76931,6 @@ const injectJsonAsMarkdownBlock = (title, json) => { exports.injectJsonAsMarkdownBlock = injectJsonAsMarkdownBlock; -/***/ }), - -/***/ 42277: -/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { - -"use strict"; - -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getRandomElement = void 0; -const chance_1 = __importDefault(__nccwpck_require__(78043)); -const chance = new chance_1.default(); -const getRandomElement = (list) => { - // Return undefined for empty lists - if (!list?.length) { - return undefined; - } - // Return first element for single item lists - if (list.length === 1) { - return list[0]; - } - // Use chance to get a random index - const randomIndex = chance.integer({ min: 0, max: list.length - 1 }); - return list[randomIndex]; -}; -exports.getRandomElement = getRandomElement; - - /***/ }), /***/ 91151: diff --git a/build/github_action/index.js b/build/github_action/index.js index 22830f76c..dc74516af 100644 --- a/build/github_action/index.js +++ b/build/github_action/index.js @@ -3815,11711 +3815,6 @@ module.exports = ({onlyFirst = false} = {}) => { }; -/***/ }), - -/***/ 78043: -/***/ ((module, exports) => { - -// Chance.js 1.1.12 -// https://chancejs.com -// (c) 2013 Victor Quinn -// Chance may be freely distributed or modified under the MIT license. - -(function () { - - // Constants - var MAX_INT = 9007199254740992; - var MIN_INT = -MAX_INT; - var NUMBERS = '0123456789'; - var CHARS_LOWER = 'abcdefghijklmnopqrstuvwxyz'; - var CHARS_UPPER = CHARS_LOWER.toUpperCase(); - var HEX_POOL = NUMBERS + "abcdef"; - - // Errors - function UnsupportedError(message) { - this.name = 'UnsupportedError'; - this.message = message || 'This feature is not supported on this platform'; - } - - UnsupportedError.prototype = new Error(); - UnsupportedError.prototype.constructor = UnsupportedError; - - // Cached array helpers - var slice = Array.prototype.slice; - - // Constructor - function Chance (seed) { - if (!(this instanceof Chance)) { - if (!seed) { seed = null; } // handle other non-truthy seeds, as described in issue #322 - return seed === null ? new Chance() : new Chance(seed); - } - - // if user has provided a function, use that as the generator - if (typeof seed === 'function') { - this.random = seed; - return this; - } - - if (arguments.length) { - // set a starting value of zero so we can add to it - this.seed = 0; - } - - // otherwise, leave this.seed blank so that MT will receive a blank - - for (var i = 0; i < arguments.length; i++) { - var seedling = 0; - if (Object.prototype.toString.call(arguments[i]) === '[object String]') { - for (var j = 0; j < arguments[i].length; j++) { - // create a numeric hash for each argument, add to seedling - var hash = 0; - for (var k = 0; k < arguments[i].length; k++) { - hash = arguments[i].charCodeAt(k) + (hash << 6) + (hash << 16) - hash; - } - seedling += hash; - } - } else { - seedling = arguments[i]; - } - this.seed += (arguments.length - i) * seedling; - } - - // If no generator function was provided, use our MT - this.mt = this.mersenne_twister(this.seed); - this.bimd5 = this.blueimp_md5(); - this.random = function () { - return this.mt.random(this.seed); - }; - - return this; - } - - Chance.prototype.VERSION = "1.1.13"; - - // Random helper functions - function initOptions(options, defaults) { - options = options || {}; - - if (defaults) { - for (var i in defaults) { - if (typeof options[i] === 'undefined') { - options[i] = defaults[i]; - } - } - } - - return options; - } - - function range(size) { - return Array.apply(null, Array(size)).map(function (_, i) {return i;}); - } - - function testRange(test, errorMessage) { - if (test) { - throw new RangeError(errorMessage); - } - } - - /** - * Encode the input string with Base64. - */ - var base64 = function() { - throw new Error('No Base64 encoder available.'); - }; - - // Select proper Base64 encoder. - (function determineBase64Encoder() { - if (typeof btoa === 'function') { - base64 = btoa; - } else if (typeof Buffer === 'function') { - base64 = function(input) { - return new Buffer(input).toString('base64'); - }; - } - })(); - - // -- Basics -- - - /** - * Return a random bool, either true or false - * - * @param {Object} [options={ likelihood: 50 }] alter the likelihood of - * receiving a true or false value back. - * @throws {RangeError} if the likelihood is out of bounds - * @returns {Bool} either true or false - */ - Chance.prototype.bool = function (options) { - // likelihood of success (true) - options = initOptions(options, {likelihood : 50}); - - // Note, we could get some minor perf optimizations by checking range - // prior to initializing defaults, but that makes code a bit messier - // and the check more complicated as we have to check existence of - // the object then existence of the key before checking constraints. - // Since the options initialization should be minor computationally, - // decision made for code cleanliness intentionally. This is mentioned - // here as it's the first occurrence, will not be mentioned again. - testRange( - options.likelihood < 0 || options.likelihood > 100, - "Chance: Likelihood accepts values from 0 to 100." - ); - - return this.random() * 100 < options.likelihood; - }; - - Chance.prototype.falsy = function (options) { - // return a random falsy value - options = initOptions(options, {pool: [false, null, 0, NaN, '', undefined]}) - var pool = options.pool, - index = this.integer({min: 0, max: pool.length - 1}), - value = pool[index]; - - return value; - } - - Chance.prototype.animal = function (options){ - //returns a random animal - options = initOptions(options); - - if(typeof options.type !== 'undefined'){ - //if user does not put in a valid animal type, user will get an error - testRange( - !this.get("animals")[options.type.toLowerCase()], - "Please pick from desert, ocean, grassland, forest, zoo, pets, farm." - ); - //if user does put in valid animal type, will return a random animal of that type - return this.pick(this.get("animals")[options.type.toLowerCase()]); - } - //if user does not put in any animal type, will return a random animal regardless - var animalTypeArray = ["desert","forest","ocean","zoo","farm","pet","grassland"]; - return this.pick(this.get("animals")[this.pick(animalTypeArray)]); - }; - - /** - * Return a random character. - * - * @param {Object} [options={}] can specify a character pool or alpha, - * numeric, symbols and casing (lower or upper) - * @returns {String} a single random character - */ - Chance.prototype.character = function (options) { - options = initOptions(options); - - var symbols = "!@#$%^&*()[]", - letters, pool; - - if (options.casing === 'lower') { - letters = CHARS_LOWER; - } else if (options.casing === 'upper') { - letters = CHARS_UPPER; - } else { - letters = CHARS_LOWER + CHARS_UPPER; - } - - if (options.pool) { - pool = options.pool; - } else { - pool = ''; - if (options.alpha) { - pool += letters; - } - if (options.numeric) { - pool += NUMBERS; - } - if (options.symbols) { - pool += symbols; - } - if (!pool) { - pool = letters + NUMBERS + symbols; - } - } - - return pool.charAt(this.natural({max: (pool.length - 1)})); - }; - - // Note, wanted to use "float" or "double" but those are both JS reserved words. - - // Note, fixed means N OR LESS digits after the decimal. This because - // It could be 14.9000 but in JavaScript, when this is cast as a number, - // the trailing zeroes are dropped. Left to the consumer if trailing zeroes are - // needed - /** - * Return a random floating point number - * - * @param {Object} [options={}] can specify a fixed precision, min, max - * @returns {Number} a single floating point number - * @throws {RangeError} Can only specify fixed or precision, not both. Also - * min cannot be greater than max - */ - Chance.prototype.floating = function (options) { - options = initOptions(options, {fixed : 4}); - testRange( - options.fixed && options.precision, - "Chance: Cannot specify both fixed and precision." - ); - - var num; - var fixed = Math.pow(10, options.fixed); - - var max = MAX_INT / fixed; - var min = -max; - - testRange( - options.min && options.fixed && options.min < min, - "Chance: Min specified is out of range with fixed. Min should be, at least, " + min - ); - testRange( - options.max && options.fixed && options.max > max, - "Chance: Max specified is out of range with fixed. Max should be, at most, " + max - ); - - options = initOptions(options, { min : min, max : max }); - - // Todo - Make this work! - // options.precision = (typeof options.precision !== "undefined") ? options.precision : false; - - num = this.integer({min: options.min * fixed, max: options.max * fixed}); - var num_fixed = (num / fixed).toFixed(options.fixed); - - return parseFloat(num_fixed); - }; - - /** - * Return a random integer - * - * NOTE the max and min are INCLUDED in the range. So: - * chance.integer({min: 1, max: 3}); - * would return either 1, 2, or 3. - * - * @param {Object} [options={}] can specify a min and/or max - * @returns {Number} a single random integer number - * @throws {RangeError} min cannot be greater than max - */ - Chance.prototype.integer = function (options) { - // 9007199254740992 (2^53) is the max integer number in JavaScript - // See: http://vq.io/132sa2j - options = initOptions(options, {min: MIN_INT, max: MAX_INT}); - testRange(options.min > options.max, "Chance: Min cannot be greater than Max."); - - return Math.floor(this.random() * (options.max - options.min + 1) + options.min); - }; - - /** - * Return a random natural - * - * NOTE the max and min are INCLUDED in the range. So: - * chance.natural({min: 1, max: 3}); - * would return either 1, 2, or 3. - * - * @param {Object} [options={}] can specify a min and/or max or a numerals count. - * @returns {Number} a single random integer number - * @throws {RangeError} min cannot be greater than max - */ - Chance.prototype.natural = function (options) { - options = initOptions(options, {min: 0, max: MAX_INT}); - if (typeof options.numerals === 'number'){ - testRange(options.numerals < 1, "Chance: Numerals cannot be less than one."); - options.min = Math.pow(10, options.numerals - 1); - options.max = Math.pow(10, options.numerals) - 1; - } - testRange(options.min < 0, "Chance: Min cannot be less than zero."); - - if (options.exclude) { - testRange(!Array.isArray(options.exclude), "Chance: exclude must be an array.") - - for (var exclusionIndex in options.exclude) { - testRange(!Number.isInteger(options.exclude[exclusionIndex]), "Chance: exclude must be numbers.") - } - - var random = options.min + this.natural({max: options.max - options.min - options.exclude.length}) - var sortedExclusions = options.exclude.sort((a, b) => a - b); - for (var sortedExclusionIndex in sortedExclusions) { - if (random < sortedExclusions[sortedExclusionIndex]) { - break - } - random++ - } - return random - } - return this.integer(options); - }; - - /** - * Return a random prime number - * - * NOTE the max and min are INCLUDED in the range. - * - * @param {Object} [options={}] can specify a min and/or max - * @returns {Number} a single random prime number - * @throws {RangeError} min cannot be greater than max nor negative - */ - Chance.prototype.prime = function (options) { - options = initOptions(options, {min: 0, max: 10000}); - testRange(options.min < 0, "Chance: Min cannot be less than zero."); - testRange(options.min > options.max, "Chance: Min cannot be greater than Max."); - - var lastPrime = data.primes[data.primes.length - 1]; - if (options.max > lastPrime) { - for (var i = lastPrime + 2; i <= options.max; ++i) { - if (this.is_prime(i)) { - data.primes.push(i); - } - } - } - var targetPrimes = data.primes.filter(function (prime) { - return prime >= options.min && prime <= options.max; - }); - return this.pick(targetPrimes); - }; - - /** - * Determine whether a given number is prime or not. - */ - Chance.prototype.is_prime = function (n) { - if (n % 1 || n < 2) { - return false; - } - if (n % 2 === 0) { - return n === 2; - } - if (n % 3 === 0) { - return n === 3; - } - var m = Math.sqrt(n); - for (var i = 5; i <= m; i += 6) { - if (n % i === 0 || n % (i + 2) === 0) { - return false; - } - } - return true; - }; - - /** - * Return a random hex number as string - * - * NOTE the max and min are INCLUDED in the range. So: - * chance.hex({min: '9', max: 'B'}); - * would return either '9', 'A' or 'B'. - * - * @param {Object} [options={}] can specify a min and/or max and/or casing - * @returns {String} a single random string hex number - * @throws {RangeError} min cannot be greater than max - */ - Chance.prototype.hex = function (options) { - options = initOptions(options, {min: 0, max: MAX_INT, casing: 'lower'}); - testRange(options.min < 0, "Chance: Min cannot be less than zero."); - var integer = this.natural({min: options.min, max: options.max}); - if (options.casing === 'upper') { - return integer.toString(16).toUpperCase(); - } - return integer.toString(16); - }; - - Chance.prototype.letter = function(options) { - options = initOptions(options, {casing: 'lower'}); - var pool = "abcdefghijklmnopqrstuvwxyz"; - var letter = this.character({pool: pool}); - if (options.casing === 'upper') { - letter = letter.toUpperCase(); - } - return letter; - } - - /** - * Return a random string - * - * @param {Object} [options={}] can specify a length or min and max - * @returns {String} a string of random length - * @throws {RangeError} length cannot be less than zero - */ - Chance.prototype.string = function (options) { - options = initOptions(options, { min: 5, max: 20 }); - - if (options.length !== 0 && !options.length) { - options.length = this.natural({ min: options.min, max: options.max }) - } - - testRange(options.length < 0, "Chance: Length cannot be less than zero."); - var length = options.length, - text = this.n(this.character, length, options); - - return text.join(""); - }; - - function CopyToken(c) { - this.c = c - } - - CopyToken.prototype = { - substitute: function () { - return this.c - } - } - - function EscapeToken(c) { - this.c = c - } - - EscapeToken.prototype = { - substitute: function () { - if (!/[{}\\]/.test(this.c)) { - throw new Error('Invalid escape sequence: "\\' + this.c + '".') - } - return this.c - } - } - - function ReplaceToken(c) { - this.c = c - } - - ReplaceToken.prototype = { - replacers: { - '#': function (chance) { return chance.character({ pool: NUMBERS }) }, - 'A': function (chance) { return chance.character({ pool: CHARS_UPPER }) }, - 'a': function (chance) { return chance.character({ pool: CHARS_LOWER }) }, - }, - - substitute: function (chance) { - var replacer = this.replacers[this.c] - if (!replacer) { - throw new Error('Invalid replacement character: "' + this.c + '".') - } - return replacer(chance) - } - } - - function parseTemplate(template) { - var tokens = [] - var mode = 'identity' - for (var i = 0; i MAX_DUPLICATES) { - throw new RangeError("Chance: num is likely too large for sample set"); - } - } - return arr; - }; - - /** - * Gives an array of n random terms - * - * @param {Function} fn the function that generates something random - * @param {Number} n number of terms to generate - * @returns {Array} an array of length `n` with items generated by `fn` - * - * There can be more parameters after these. All additional parameters are provided to the given function - */ - Chance.prototype.n = function(fn, n) { - testRange( - typeof fn !== "function", - "Chance: The first argument must be a function." - ); - - if (typeof n === 'undefined') { - n = 1; - } - var i = n, arr = [], params = slice.call(arguments, 2); - - // Providing a negative count should result in a noop. - i = Math.max( 0, i ); - - for (null; i--; null) { - arr.push(fn.apply(this, params)); - } - - return arr; - }; - - // H/T to SO for this one: http://vq.io/OtUrZ5 - Chance.prototype.pad = function (number, width, pad) { - // Default pad to 0 if none provided - pad = pad || '0'; - // Convert number to a string - number = number + ''; - return number.length >= width ? number : new Array(width - number.length + 1).join(pad) + number; - }; - - // DEPRECATED on 2015-10-01 - Chance.prototype.pick = function (arr, count) { - if (arr.length === 0) { - throw new RangeError("Chance: Cannot pick() from an empty array"); - } - if (!count || count === 1) { - return arr[this.natural({max: arr.length - 1})]; - } else { - return this.shuffle(arr).slice(0, count); - } - }; - - // Given an array, returns a single random element - Chance.prototype.pickone = function (arr) { - if (arr.length === 0) { - throw new RangeError("Chance: Cannot pickone() from an empty array"); - } - return arr[this.natural({max: arr.length - 1})]; - }; - - // Given an array, returns a random set with 'count' elements - Chance.prototype.pickset = function (arr, count) { - if (count === 0) { - return []; - } - if (arr.length === 0) { - throw new RangeError("Chance: Cannot pickset() from an empty array"); - } - if (count < 0) { - throw new RangeError("Chance: Count must be a positive number"); - } - if (!count || count === 1) { - return [ this.pickone(arr) ]; - } else { - var array = arr.slice(0); - var end = array.length; - - return this.n(function () { - var index = this.natural({max: --end}); - var value = array[index]; - array[index] = array[end]; - return value; - }, Math.min(end, count)); - } - }; - - Chance.prototype.shuffle = function (arr) { - var new_array = [], - j = 0, - length = Number(arr.length), - source_indexes = range(length), - last_source_index = length - 1, - selected_source_index; - - for (var i = 0; i < length; i++) { - // Pick a random index from the array - selected_source_index = this.natural({max: last_source_index}); - j = source_indexes[selected_source_index]; - - // Add it to the new array - new_array[i] = arr[j]; - - // Mark the source index as used - source_indexes[selected_source_index] = source_indexes[last_source_index]; - last_source_index -= 1; - } - - return new_array; - }; - - // Returns a single item from an array with relative weighting of odds - Chance.prototype.weighted = function (arr, weights, trim) { - if (arr.length !== weights.length) { - throw new RangeError("Chance: Length of array and weights must match"); - } - - // scan weights array and sum valid entries - var sum = 0; - var val; - for (var weightIndex = 0; weightIndex < weights.length; ++weightIndex) { - val = weights[weightIndex]; - if (isNaN(val)) { - throw new RangeError("Chance: All weights must be numbers"); - } - - if (val > 0) { - sum += val; - } - } - - if (sum === 0) { - throw new RangeError("Chance: No valid entries in array weights"); - } - - // select a value within range - var selected = this.random() * sum; - - // find array entry corresponding to selected value - var total = 0; - var lastGoodIdx = -1; - var chosenIdx; - for (weightIndex = 0; weightIndex < weights.length; ++weightIndex) { - val = weights[weightIndex]; - total += val; - if (val > 0) { - if (selected <= total) { - chosenIdx = weightIndex; - break; - } - lastGoodIdx = weightIndex; - } - - // handle any possible rounding error comparison to ensure something is picked - if (weightIndex === (weights.length - 1)) { - chosenIdx = lastGoodIdx; - } - } - - var chosen = arr[chosenIdx]; - trim = (typeof trim === 'undefined') ? false : trim; - if (trim) { - arr.splice(chosenIdx, 1); - weights.splice(chosenIdx, 1); - } - - return chosen; - }; - - // -- End Helpers -- - - // -- Text -- - - Chance.prototype.paragraph = function (options) { - options = initOptions(options); - - var sentences = options.sentences || this.natural({min: 3, max: 7}), - sentence_array = this.n(this.sentence, sentences), - separator = options.linebreak === true ? '\n' : ' '; - - return sentence_array.join(separator); - }; - - // Could get smarter about this than generating random words and - // chaining them together. Such as: http://vq.io/1a5ceOh - Chance.prototype.sentence = function (options) { - options = initOptions(options); - - var words = options.words || this.natural({min: 12, max: 18}), - punctuation = options.punctuation, - text, word_array = this.n(this.word, words); - - text = word_array.join(' '); - - // Capitalize first letter of sentence - text = this.capitalize(text); - - // Make sure punctuation has a usable value - if (punctuation !== false && !/^[.?;!:]$/.test(punctuation)) { - punctuation = '.'; - } - - // Add punctuation mark - if (punctuation) { - text += punctuation; - } - - return text; - }; - - Chance.prototype.syllable = function (options) { - options = initOptions(options); - - var length = options.length || this.natural({min: 2, max: 3}), - consonants = 'bcdfghjklmnprstvwz', // consonants except hard to speak ones - vowels = 'aeiou', // vowels - all = consonants + vowels, // all - text = '', - chr; - - // I'm sure there's a more elegant way to do this, but this works - // decently well. - for (var i = 0; i < length; i++) { - if (i === 0) { - // First character can be anything - chr = this.character({pool: all}); - } else if (consonants.indexOf(chr) === -1) { - // Last character was a vowel, now we want a consonant - chr = this.character({pool: consonants}); - } else { - // Last character was a consonant, now we want a vowel - chr = this.character({pool: vowels}); - } - - text += chr; - } - - if (options.capitalize) { - text = this.capitalize(text); - } - - return text; - }; - - Chance.prototype.word = function (options) { - options = initOptions(options); - - testRange( - options.syllables && options.length, - "Chance: Cannot specify both syllables AND length." - ); - - var syllables = options.syllables || this.natural({min: 1, max: 3}), - text = ''; - - if (options.length) { - // Either bound word by length - do { - text += this.syllable(); - } while (text.length < options.length); - text = text.substring(0, options.length); - } else { - // Or by number of syllables - for (var i = 0; i < syllables; i++) { - text += this.syllable(); - } - } - - if (options.capitalize) { - text = this.capitalize(text); - } - - return text; - }; - - Chance.prototype.emoji = function (options) { - options = initOptions(options, { category: "all", length: 1 }); - - testRange( - options.length < 1 || BigInt(options.length) > BigInt(MAX_INT), - "Chance: length must be between 1 and " + String(MAX_INT) - ); - - var emojis = this.get("emojis"); - - if (options.category === "all") { - options.category = this.pickone(Object.keys(emojis)); - } - - var emojisForCategory = emojis[options.category]; - - testRange( - emojisForCategory === undefined, - "Chance: Unrecognised emoji category: [" + options.category + "]." - ); - - return this.pickset(emojisForCategory, options.length) - .map(function (codePoint) { - return String.fromCodePoint(codePoint); - }).join(""); - }; - - // -- End Text -- - - // -- Person -- - - Chance.prototype.age = function (options) { - options = initOptions(options); - var ageRange; - - switch (options.type) { - case 'child': - ageRange = {min: 0, max: 12}; - break; - case 'teen': - ageRange = {min: 13, max: 19}; - break; - case 'adult': - ageRange = {min: 18, max: 65}; - break; - case 'senior': - ageRange = {min: 65, max: 100}; - break; - case 'all': - ageRange = {min: 0, max: 100}; - break; - default: - ageRange = {min: 18, max: 65}; - break; - } - - return this.natural(ageRange); - }; - - Chance.prototype.birthday = function (options) { - var age = this.age(options); - var now = new Date() - var currentYear = now.getFullYear(); - - if (options && options.type) { - var min = new Date(); - var max = new Date(); - min.setFullYear(currentYear - age - 1); - max.setFullYear(currentYear - age); - - options = initOptions(options, { - min: min, - max: max - }); - } else if (options && ((options.minAge !== undefined) || (options.maxAge !== undefined))) { - testRange(options.minAge < 0, "Chance: MinAge cannot be less than zero."); - testRange(options.minAge > options.maxAge, "Chance: MinAge cannot be greater than MaxAge."); - - var minAge = options.minAge !== undefined ? options.minAge : 0; - var maxAge = options.maxAge !== undefined ? options.maxAge : 100; - - var minDate = new Date(currentYear - maxAge - 1, now.getMonth(), now.getDate()); - var maxDate = new Date(currentYear - minAge, now.getMonth(), now.getDate()); - - minDate.setDate(minDate.getDate() +1); - - maxDate.setDate(maxDate.getDate() +1); - maxDate.setMilliseconds(maxDate.getMilliseconds() -1); - - options = initOptions(options, { - min: minDate, - max: maxDate - }); - } else { - options = initOptions(options, { - year: currentYear - age - }); - } - - return this.date(options); - }; - - // CPF; ID to identify taxpayers in Brazil - Chance.prototype.cpf = function (options) { - options = initOptions(options, { - formatted: true - }); - - var n = this.n(this.natural, 9, { max: 9 }); - var d1 = n[8]*2+n[7]*3+n[6]*4+n[5]*5+n[4]*6+n[3]*7+n[2]*8+n[1]*9+n[0]*10; - d1 = 11 - (d1 % 11); - if (d1>=10) { - d1 = 0; - } - var d2 = d1*2+n[8]*3+n[7]*4+n[6]*5+n[5]*6+n[4]*7+n[3]*8+n[2]*9+n[1]*10+n[0]*11; - d2 = 11 - (d2 % 11); - if (d2>=10) { - d2 = 0; - } - var cpf = ''+n[0]+n[1]+n[2]+'.'+n[3]+n[4]+n[5]+'.'+n[6]+n[7]+n[8]+'-'+d1+d2; - return options.formatted ? cpf : cpf.replace(/\D/g,''); - }; - - // CNPJ: ID to identify companies in Brazil - Chance.prototype.cnpj = function (options) { - options = initOptions(options, { - formatted: true - }); - - var n = this.n(this.natural, 12, { max: 12 }); - var d1 = n[11]*2+n[10]*3+n[9]*4+n[8]*5+n[7]*6+n[6]*7+n[5]*8+n[4]*9+n[3]*2+n[2]*3+n[1]*4+n[0]*5; - d1 = 11 - (d1 % 11); - if (d1<2) { - d1 = 0; - } - var d2 = d1*2+n[11]*3+n[10]*4+n[9]*5+n[8]*6+n[7]*7+n[6]*8+n[5]*9+n[4]*2+n[3]*3+n[2]*4+n[1]*5+n[0]*6; - d2 = 11 - (d2 % 11); - if (d2<2) { - d2 = 0; - } - var cnpj = ''+n[0]+n[1]+'.'+n[2]+n[3]+n[4]+'.'+n[5]+n[6]+n[7]+'/'+n[8]+n[9]+n[10]+n[11]+'-'+d1+d2; - return options.formatted ? cnpj : cnpj.replace(/\D/g,''); - }; - - Chance.prototype.first = function (options) { - options = initOptions(options, {gender: this.gender(), nationality: 'en'}); - return this.pick(this.get("firstNames")[options.gender.toLowerCase()][options.nationality.toLowerCase()]); - }; - - Chance.prototype.profession = function (options) { - options = initOptions(options); - if(options.rank){ - return this.pick(['Apprentice ', 'Junior ', 'Senior ', 'Lead ']) + this.pick(this.get("profession")); - } else{ - return this.pick(this.get("profession")); - } - }; - - Chance.prototype.company = function (){ - return this.pick(this.get("company")); - }; - - Chance.prototype.gender = function (options) { - options = initOptions(options, {extraGenders: []}); - return this.pick(['Male', 'Female'].concat(options.extraGenders)); - }; - - Chance.prototype.last = function (options) { - options = initOptions(options, {nationality: '*'}); - if (options.nationality === "*") { - var allLastNames = [] - var lastNames = this.get("lastNames") - Object.keys(lastNames).forEach(function(key){ - allLastNames = allLastNames.concat(lastNames[key]) - }) - return this.pick(allLastNames) - } - else { - return this.pick(this.get("lastNames")[options.nationality.toLowerCase()]); - } - - }; - - Chance.prototype.israelId=function(){ - var x=this.string({pool: '0123456789',length:8}); - var y=0; - for (var i=0;i hex - * -> rgb - * -> rgba - * -> 0x - * -> named color - * - * #Examples: - * =============================================== - * * Geerate random hex color - * chance.color() => '#79c157' / 'rgb(110,52,164)' / '0x67ae0b' / '#e2e2e2' / '#29CFA7' - * - * * Generate Hex based color value - * chance.color({format: 'hex'}) => '#d67118' - * - * * Generate simple rgb value - * chance.color({format: 'rgb'}) => 'rgb(110,52,164)' - * - * * Generate Ox based color value - * chance.color({format: '0x'}) => '0x67ae0b' - * - * * Generate graiscale based value - * chance.color({grayscale: true}) => '#e2e2e2' - * - * * Return valide color name - * chance.color({format: 'name'}) => 'red' - * - * * Make color uppercase - * chance.color({casing: 'upper'}) => '#29CFA7' - * - * * Min Max values for RGBA - * var light_red = chance.color({format: 'hex', min_red: 200, max_red: 255, max_green: 0, max_blue: 0, min_alpha: .2, max_alpha: .3}); - * - * @param [object] options - * @return [string] color value - */ - Chance.prototype.color = function (options) { - function gray(value, delimiter) { - return [value, value, value].join(delimiter || ''); - } - - function rgb(hasAlpha) { - var rgbValue = (hasAlpha) ? 'rgba' : 'rgb'; - var alphaChannel = (hasAlpha) ? (',' + this.floating({min:min_alpha, max:max_alpha})) : ""; - var colorValue = (isGrayscale) ? (gray(this.natural({min: min_rgb, max: max_rgb}), ',')) : (this.natural({min: min_green, max: max_green}) + ',' + this.natural({min: min_blue, max: max_blue}) + ',' + this.natural({max: 255})); - return rgbValue + '(' + colorValue + alphaChannel + ')'; - } - - function hex(start, end, withHash) { - var symbol = (withHash) ? "#" : ""; - var hexstring = ""; - - if (isGrayscale) { - hexstring = gray(this.pad(this.hex({min: min_rgb, max: max_rgb}), 2)); - if (options.format === "shorthex") { - hexstring = gray(this.hex({min: 0, max: 15})); - } - } - else { - if (options.format === "shorthex") { - hexstring = this.pad(this.hex({min: Math.floor(min_red / 16), max: Math.floor(max_red / 16)}), 1) + this.pad(this.hex({min: Math.floor(min_green / 16), max: Math.floor(max_green / 16)}), 1) + this.pad(this.hex({min: Math.floor(min_blue / 16), max: Math.floor(max_blue / 16)}), 1); - } - else if (min_red !== undefined || max_red !== undefined || min_green !== undefined || max_green !== undefined || min_blue !== undefined || max_blue !== undefined) { - hexstring = this.pad(this.hex({min: min_red, max: max_red}), 2) + this.pad(this.hex({min: min_green, max: max_green}), 2) + this.pad(this.hex({min: min_blue, max: max_blue}), 2); - } - else { - hexstring = this.pad(this.hex({min: min_rgb, max: max_rgb}), 2) + this.pad(this.hex({min: min_rgb, max: max_rgb}), 2) + this.pad(this.hex({min: min_rgb, max: max_rgb}), 2); - } - } - - return symbol + hexstring; - } - - options = initOptions(options, { - format: this.pick(['hex', 'shorthex', 'rgb', 'rgba', '0x', 'name']), - grayscale: false, - casing: 'lower', - min: 0, - max: 255, - min_red: undefined, - max_red: undefined, - min_green: undefined, - max_green: undefined, - min_blue: undefined, - max_blue: undefined, - min_alpha: 0, - max_alpha: 1 - }); - - var isGrayscale = options.grayscale; - var min_rgb = options.min; - var max_rgb = options.max; - var min_red = options.min_red; - var max_red = options.max_red; - var min_green = options.min_green; - var max_green = options.max_green; - var min_blue = options.min_blue; - var max_blue = options.max_blue; - var min_alpha = options.min_alpha; - var max_alpha = options.max_alpha; - if (options.min_red === undefined) { min_red = min_rgb; } - if (options.max_red === undefined) { max_red = max_rgb; } - if (options.min_green === undefined) { min_green = min_rgb; } - if (options.max_green === undefined) { max_green = max_rgb; } - if (options.min_blue === undefined) { min_blue = min_rgb; } - if (options.max_blue === undefined) { max_blue = max_rgb; } - if (options.min_alpha === undefined) { min_alpha = 0; } - if (options.max_alpha === undefined) { max_alpha = 1; } - if (isGrayscale && min_rgb === 0 && max_rgb === 255 && min_red !== undefined && max_red !== undefined) { - min_rgb = ((min_red + min_green + min_blue) / 3); - max_rgb = ((max_red + max_green + max_blue) / 3); - } - var colorValue; - - if (options.format === 'hex') { - colorValue = hex.call(this, 2, 6, true); - } - else if (options.format === 'shorthex') { - colorValue = hex.call(this, 1, 3, true); - } - else if (options.format === 'rgb') { - colorValue = rgb.call(this, false); - } - else if (options.format === 'rgba') { - colorValue = rgb.call(this, true); - } - else if (options.format === '0x') { - colorValue = '0x' + hex.call(this, 2, 6); - } - else if(options.format === 'name') { - return this.pick(this.get("colorNames")); - } - else { - throw new RangeError('Invalid format provided. Please provide one of "hex", "shorthex", "rgb", "rgba", "0x" or "name".'); - } - - if (options.casing === 'upper' ) { - colorValue = colorValue.toUpperCase(); - } - - return colorValue; - }; - - Chance.prototype.domain = function (options) { - options = initOptions(options); - return this.word() + '.' + (options.tld || this.tld()); - }; - - Chance.prototype.email = function (options) { - options = initOptions(options); - return this.word({length: options.length}) + '@' + (options.domain || this.domain()); - }; - - /** - * #Description: - * =============================================== - * Generate a random Facebook id, aka fbid. - * - * NOTE: At the moment (Sep 2017), Facebook ids are - * "numeric strings" of length 16. - * However, Facebook Graph API documentation states that - * "it is extremely likely to change over time". - * @see https://developers.facebook.com/docs/graph-api/overview/ - * - * #Examples: - * =============================================== - * chance.fbid() => '1000035231661304' - * - * @return [string] facebook id - */ - Chance.prototype.fbid = function () { - return '10000' + this.string({pool: "1234567890", length: 11}); - }; - - Chance.prototype.google_analytics = function () { - var account = this.pad(this.natural({max: 999999}), 6); - var property = this.pad(this.natural({max: 99}), 2); - - return 'UA-' + account + '-' + property; - }; - - Chance.prototype.hashtag = function () { - return '#' + this.word(); - }; - - Chance.prototype.ip = function () { - // Todo: This could return some reserved IPs. See http://vq.io/137dgYy - // this should probably be updated to account for that rare as it may be - return this.natural({min: 1, max: 254}) + '.' + - this.natural({max: 255}) + '.' + - this.natural({max: 255}) + '.' + - this.natural({min: 1, max: 254}); - }; - - Chance.prototype.ipv6 = function () { - var ip_addr = this.n(this.hash, 8, {length: 4}); - - return ip_addr.join(":"); - }; - - Chance.prototype.klout = function () { - return this.natural({min: 1, max: 99}); - }; - - Chance.prototype.mac = function (options) { - // Todo: This could also be extended to EUI-64 based MACs - // (https://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml#ethernet-numbers-4) - // Todo: This can return some reserved MACs (similar to IP function) - // this should probably be updated to account for that rare as it may be - options = initOptions(options, { delimiter: ':' }); - return this.pad(this.natural({max: 255}).toString(16),2) + options.delimiter + - this.pad(this.natural({max: 255}).toString(16),2) + options.delimiter + - this.pad(this.natural({max: 255}).toString(16),2) + options.delimiter + - this.pad(this.natural({max: 255}).toString(16),2) + options.delimiter + - this.pad(this.natural({max: 255}).toString(16),2) + options.delimiter + - this.pad(this.natural({max: 255}).toString(16),2); - }; - - Chance.prototype.semver = function (options) { - options = initOptions(options, { include_prerelease: true }); - - var range = this.pickone(["^", "~", "<", ">", "<=", ">=", "="]); - if (options.range) { - range = options.range; - } - - var prerelease = ""; - if (options.include_prerelease) { - prerelease = this.weighted(["", "-dev", "-beta", "-alpha"], [50, 10, 5, 1]); - } - return range + this.rpg('3d10').join('.') + prerelease; - }; - - Chance.prototype.tlds = function () { - return ['com', 'org', 'edu', 'gov', 'co.uk', 'net', 'io', 'ac', 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'ao', 'aq', 'ar', 'as', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh', 'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cc', 'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'cr', 'cu', 'cv', 'cw', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'ee', 'eg', 'eh', 'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq', 'ir', 'is', 'it', 'je', 'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', 'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mk', 'ml', 'mm', 'mn', 'mo', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'nc', 'ne', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'pa', 'pe', 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', 'ss', 'st', 'su', 'sv', 'sx', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th', 'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', 'uk', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye', 'yt', 'za', 'zm', 'zw']; - }; - - Chance.prototype.tld = function () { - return this.pick(this.tlds()); - }; - - Chance.prototype.twitter = function () { - return '@' + this.word(); - }; - - Chance.prototype.url = function (options) { - options = initOptions(options, { protocol: "http", domain: this.domain(options), domain_prefix: "", path: this.word(), extensions: []}); - - var extension = options.extensions.length > 0 ? "." + this.pick(options.extensions) : ""; - var domain = options.domain_prefix ? options.domain_prefix + "." + options.domain : options.domain; - - return options.protocol + "://" + domain + "/" + options.path + extension; - }; - - Chance.prototype.port = function() { - return this.integer({min: 0, max: 65535}); - }; - - Chance.prototype.locale = function (options) { - options = initOptions(options); - if (options.region){ - return this.pick(this.get("locale_regions")); - } else { - return this.pick(this.get("locale_languages")); - } - }; - - Chance.prototype.locales = function (options) { - options = initOptions(options); - if (options.region){ - return this.get("locale_regions"); - } else { - return this.get("locale_languages"); - } - }; - - Chance.prototype.loremPicsum = function (options) { - options = initOptions(options, { width: 500, height: 500, greyscale: false, blurred: false }); - - var greyscale = options.greyscale ? 'g/' : ''; - var query = options.blurred ? '/?blur' : '/?random'; - - return 'https://picsum.photos/' + greyscale + options.width + '/' + options.height + query; - } - - // -- End Web -- - - // -- Location -- - - Chance.prototype.address = function (options) { - options = initOptions(options); - return this.natural({min: 5, max: 2000}) + ' ' + this.street(options); - }; - - Chance.prototype.altitude = function (options) { - options = initOptions(options, {fixed: 5, min: 0, max: 8848}); - return this.floating({ - min: options.min, - max: options.max, - fixed: options.fixed - }); - }; - - Chance.prototype.areacode = function (options) { - options = initOptions(options, {parens : true}); - // Don't want area codes to start with 1, or have a 9 as the second digit - var areacode = options.exampleNumber ? - "555" : - this.natural({min: 2, max: 9}).toString() + - this.natural({min: 0, max: 8}).toString() + - this.natural({min: 0, max: 9}).toString(); - - return options.parens ? '(' + areacode + ')' : areacode; - }; - - Chance.prototype.city = function () { - return this.capitalize(this.word({syllables: 3})); - }; - - Chance.prototype.coordinates = function (options) { - return this.latitude(options) + ', ' + this.longitude(options); - }; - - Chance.prototype.countries = function () { - return this.get("countries"); - }; - - Chance.prototype.country = function (options) { - options = initOptions(options); - var country = this.pick(this.countries()); - return options.raw ? country : options.full ? country.name : country.abbreviation; - }; - - Chance.prototype.depth = function (options) { - options = initOptions(options, {fixed: 5, min: -10994, max: 0}); - return this.floating({ - min: options.min, - max: options.max, - fixed: options.fixed - }); - }; - - Chance.prototype.geohash = function (options) { - options = initOptions(options, { length: 7 }); - return this.string({ length: options.length, pool: '0123456789bcdefghjkmnpqrstuvwxyz' }); - }; - - Chance.prototype.geojson = function (options) { - return this.latitude(options) + ', ' + this.longitude(options) + ', ' + this.altitude(options); - }; - - Chance.prototype.latitude = function (options) { - // Constants - Formats - var [DDM, DMS, DD] = ['ddm', 'dms', 'dd']; - - options = initOptions( -options, - options && options.format && [DDM, DMS].includes(options.format.toLowerCase()) ? - {min: 0, max: 89, fixed: 4} : - {fixed: 5, min: -90, max: 90, format: DD} -); - - var format = options.format.toLowerCase(); - - if (format === DDM || format === DMS) { - testRange(options.min < 0 || options.min > 89, "Chance: Min specified is out of range. Should be between 0 - 89"); - testRange(options.max < 0 || options.max > 89, "Chance: Max specified is out of range. Should be between 0 - 89"); - testRange(options.fixed > 4, 'Chance: Fixed specified should be below or equal to 4'); - } - - switch (format) { - case DDM: { - return this.integer({min: options.min, max: options.max}) + '°' + - this.floating({min: 0, max: 59, fixed: options.fixed}); - } - case DMS: { - return this.integer({min: options.min, max: options.max}) + '°' + - this.integer({min: 0, max: 59}) + '’' + - this.floating({min: 0, max: 59, fixed: options.fixed}) + '”'; - } - case DD: - default: { - return this.floating({min: options.min, max: options.max, fixed: options.fixed}); - } - } - }; - - Chance.prototype.longitude = function (options) { - // Constants - Formats - var [DDM, DMS, DD] = ['ddm', 'dms', 'dd']; - - options = initOptions( -options, - options && options.format && [DDM, DMS].includes(options.format.toLowerCase()) ? - {min: 0, max: 179, fixed: 4} : - {fixed: 5, min: -180, max: 180, format: DD} -); - - var format = options.format.toLowerCase(); - - if (format === DDM || format === DMS) { - testRange(options.min < 0 || options.min > 179, "Chance: Min specified is out of range. Should be between 0 - 179"); - testRange(options.max < 0 || options.max > 179, "Chance: Max specified is out of range. Should be between 0 - 179"); - testRange(options.fixed > 4, 'Chance: Fixed specified should be below or equal to 4'); - } - - switch (format) { - case DDM: { - return this.integer({min: options.min, max: options.max}) + '°' + - this.floating({min: 0, max: 59.9999, fixed: options.fixed}) - } - case DMS: { - return this.integer({min: options.min, max: options.max}) + '°' + - this.integer({min: 0, max: 59}) + '’' + - this.floating({min: 0, max: 59.9999, fixed: options.fixed}) + '”'; - } - case DD: - default: { - return this.floating({min: options.min, max: options.max, fixed: options.fixed}); - } - } - }; - - Chance.prototype.phone = function (options) { - var self = this, - numPick, - ukNum = function (parts) { - var section = []; - //fills the section part of the phone number with random numbers. - parts.sections.forEach(function(n) { - section.push(self.string({ pool: '0123456789', length: n})); - }); - return parts.area + section.join(' '); - }; - options = initOptions(options, { - formatted: true, - country: 'us', - mobile: false, - exampleNumber: false, - }); - if (!options.formatted) { - options.parens = false; - } - var phone; - switch (options.country) { - case 'fr': - if (!options.mobile) { - numPick = this.pick([ - // Valid zone and département codes. - '01' + this.pick(['30', '34', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '53', '55', '56', '58', '60', '64', '69', '70', '72', '73', '74', '75', '76', '77', '78', '79', '80', '81', '82', '83']) + self.string({ pool: '0123456789', length: 6}), - '02' + this.pick(['14', '18', '22', '23', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '40', '41', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '56', '57', '61', '62', '69', '72', '76', '77', '78', '85', '90', '96', '97', '98', '99']) + self.string({ pool: '0123456789', length: 6}), - '03' + this.pick(['10', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '39', '44', '45', '51', '52', '54', '55', '57', '58', '59', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '70', '71', '72', '73', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '90']) + self.string({ pool: '0123456789', length: 6}), - '04' + this.pick(['11', '13', '15', '20', '22', '26', '27', '30', '32', '34', '37', '42', '43', '44', '50', '56', '57', '63', '66', '67', '68', '69', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '80', '81', '82', '83', '84', '85', '86', '88', '89', '90', '91', '92', '93', '94', '95', '97', '98']) + self.string({ pool: '0123456789', length: 6}), - '05' + this.pick(['08', '16', '17', '19', '24', '31', '32', '33', '34', '35', '40', '45', '46', '47', '49', '53', '55', '56', '57', '58', '59', '61', '62', '63', '64', '65', '67', '79', '81', '82', '86', '87', '90', '94']) + self.string({ pool: '0123456789', length: 6}), - '09' + self.string({ pool: '0123456789', length: 8}), - ]); - phone = options.formatted ? numPick.match(/../g).join(' ') : numPick; - } else { - numPick = this.pick(['06', '07']) + self.string({ pool: '0123456789', length: 8}); - phone = options.formatted ? numPick.match(/../g).join(' ') : numPick; - } - break; - case 'uk': - if (!options.mobile) { - numPick = this.pick([ - //valid area codes of major cities/counties followed by random numbers in required format. - - { area: '01' + this.character({ pool: '234569' }) + '1 ', sections: [3,4] }, - { area: '020 ' + this.character({ pool: '378' }), sections: [3,4] }, - { area: '023 ' + this.character({ pool: '89' }), sections: [3,4] }, - { area: '024 7', sections: [3,4] }, - { area: '028 ' + this.pick(['25','28','37','71','82','90','92','95']), sections: [2,4] }, - { area: '012' + this.pick(['04','08','54','76','97','98']) + ' ', sections: [6] }, - { area: '013' + this.pick(['63','64','84','86']) + ' ', sections: [6] }, - { area: '014' + this.pick(['04','20','60','61','80','88']) + ' ', sections: [6] }, - { area: '015' + this.pick(['24','27','62','66']) + ' ', sections: [6] }, - { area: '016' + this.pick(['06','29','35','47','59','95']) + ' ', sections: [6] }, - { area: '017' + this.pick(['26','44','50','68']) + ' ', sections: [6] }, - { area: '018' + this.pick(['27','37','84','97']) + ' ', sections: [6] }, - { area: '019' + this.pick(['00','05','35','46','49','63','95']) + ' ', sections: [6] } - ]); - phone = options.formatted ? ukNum(numPick) : ukNum(numPick).replace(' ', '', 'g'); - } else { - numPick = this.pick([ - { area: '07' + this.pick(['4','5','7','8','9']), sections: [2,6] }, - { area: '07624 ', sections: [6] } - ]); - phone = options.formatted ? ukNum(numPick) : ukNum(numPick).replace(' ', ''); - } - break; - case 'za': - if (!options.mobile) { - numPick = this.pick([ - '01' + this.pick(['0', '1', '2', '3', '4', '5', '6', '7', '8']) + self.string({ pool: '0123456789', length: 7}), - '02' + this.pick(['1', '2', '3', '4', '7', '8']) + self.string({ pool: '0123456789', length: 7}), - '03' + this.pick(['1', '2', '3', '5', '6', '9']) + self.string({ pool: '0123456789', length: 7}), - '04' + this.pick(['1', '2', '3', '4', '5','6','7', '8','9']) + self.string({ pool: '0123456789', length: 7}), - '05' + this.pick(['1', '3', '4', '6', '7', '8']) + self.string({ pool: '0123456789', length: 7}), - ]); - phone = options.formatted || numPick; - } else { - numPick = this.pick([ - '060' + this.pick(['3','4','5','6','7','8','9']) + self.string({ pool: '0123456789', length: 6}), - '061' + this.pick(['0','1','2','3','4','5','8']) + self.string({ pool: '0123456789', length: 6}), - '06' + self.string({ pool: '0123456789', length: 7}), - '071' + this.pick(['0','1','2','3','4','5','6','7','8','9']) + self.string({ pool: '0123456789', length: 6}), - '07' + this.pick(['2','3','4','6','7','8','9']) + self.string({ pool: '0123456789', length: 7}), - '08' + this.pick(['0','1','2','3','4','5']) + self.string({ pool: '0123456789', length: 7}), - ]); - phone = options.formatted || numPick; - } - break; - case 'us': - var areacode = this.areacode(options).toString(); - var exchange = this.natural({ min: 2, max: 9 }).toString() + - this.natural({ min: 0, max: 9 }).toString() + - this.natural({ min: 0, max: 9 }).toString(); - var subscriber = this.natural({ min: 1000, max: 9999 }).toString(); // this could be random [0-9]{4} - phone = options.formatted ? areacode + ' ' + exchange + '-' + subscriber : areacode + exchange + subscriber; - break; - case 'br': - var areaCode = this.pick(["11", "12", "13", "14", "15", "16", "17", "18", "19", "21", "22", "24", "27", "28", "31", "32", "33", "34", "35", "37", "38", "41", "42", "43", "44", "45", "46", "47", "48", "49", "51", "53", "54", "55", "61", "62", "63", "64", "65", "66", "67", "68", "69", "71", "73", "74", "75", "77", "79", "81", "82", "83", "84", "85", "86", "87", "88", "89", "91", "92", "93", "94", "95", "96", "97", "98", "99"]); - var prefix; - if (options.mobile) { - // Brasilian official reference (mobile): http://www.anatel.gov.br/setorregulado/plano-de-numeracao-brasileiro?id=330 - prefix = '9' + self.string({ pool: '0123456789', length: 4}); - } else { - // Brasilian official reference: http://www.anatel.gov.br/setorregulado/plano-de-numeracao-brasileiro?id=331 - prefix = this.natural({ min: 2000, max: 5999 }).toString(); - } - var mcdu = self.string({ pool: '0123456789', length: 4}); - phone = options.formatted ? '(' + areaCode + ') ' + prefix + '-' + mcdu : areaCode + prefix + mcdu; - break; - } - return phone; - }; - - Chance.prototype.postal = function () { - // Postal District - var pd = this.character({pool: "XVTSRPNKLMHJGECBA"}); - // Forward Sortation Area (FSA) - var fsa = pd + this.natural({max: 9}) + this.character({alpha: true, casing: "upper"}); - // Local Delivery Unut (LDU) - var ldu = this.natural({max: 9}) + this.character({alpha: true, casing: "upper"}) + this.natural({max: 9}); - - return fsa + " " + ldu; - }; - - Chance.prototype.postcode = function () { - // Area - var area = this.pick(this.get("postcodeAreas")).code; - // District - var district = this.natural({max: 9}); - // Sub-District - var subDistrict = this.bool() ? this.character({alpha: true, casing: "upper"}) : ""; - // Outward Code - var outward = area + district + subDistrict; - // Sector - var sector = this.natural({max: 9}); - // Unit - var unit = this.character({alpha: true, casing: "upper"}) + this.character({alpha: true, casing: "upper"}); - // Inward Code - var inward = sector + unit; - - return outward + " " + inward; - }; - - Chance.prototype.counties = function (options) { - options = initOptions(options, { country: 'uk' }); - return this.get("counties")[options.country.toLowerCase()]; - }; - - Chance.prototype.county = function (options) { - return this.pick(this.counties(options)).name; - }; - - Chance.prototype.provinces = function (options) { - options = initOptions(options, { country: 'ca' }); - return this.get("provinces")[options.country.toLowerCase()]; - }; - - Chance.prototype.province = function (options) { - return (options && options.full) ? - this.pick(this.provinces(options)).name : - this.pick(this.provinces(options)).abbreviation; - }; - - Chance.prototype.state = function (options) { - return (options && options.full) ? - this.pick(this.states(options)).name : - this.pick(this.states(options)).abbreviation; - }; - - Chance.prototype.states = function (options) { - options = initOptions(options, { country: 'us', us_states_and_dc: true } ); - - var states; - - switch (options.country.toLowerCase()) { - case 'us': - var us_states_and_dc = this.get("us_states_and_dc"), - territories = this.get("territories"), - armed_forces = this.get("armed_forces"); - - states = []; - - if (options.us_states_and_dc) { - states = states.concat(us_states_and_dc); - } - if (options.territories) { - states = states.concat(territories); - } - if (options.armed_forces) { - states = states.concat(armed_forces); - } - break; - case 'it': - case 'mx': - states = this.get("country_regions")[options.country.toLowerCase()]; - break; - case 'uk': - states = this.get("counties")[options.country.toLowerCase()]; - break; - } - - return states; - }; - - Chance.prototype.street = function (options) { - options = initOptions(options, { country: 'us', syllables: 2 }); - var street; - - switch (options.country.toLowerCase()) { - case 'us': - street = this.word({ syllables: options.syllables }); - street = this.capitalize(street); - street += ' '; - street += options.short_suffix ? - this.street_suffix(options).abbreviation : - this.street_suffix(options).name; - break; - case 'it': - street = this.word({ syllables: options.syllables }); - street = this.capitalize(street); - street = (options.short_suffix ? - this.street_suffix(options).abbreviation : - this.street_suffix(options).name) + " " + street; - break; - } - return street; - }; - - Chance.prototype.street_suffix = function (options) { - options = initOptions(options, { country: 'us' }); - return this.pick(this.street_suffixes(options)); - }; - - Chance.prototype.street_suffixes = function (options) { - options = initOptions(options, { country: 'us' }); - // These are the most common suffixes. - return this.get("street_suffixes")[options.country.toLowerCase()]; - }; - - // Note: only returning US zip codes, internationalization will be a whole - // other beast to tackle at some point. - Chance.prototype.zip = function (options) { - var zip = this.n(this.natural, 5, {max: 9}); - - if (options && options.plusfour === true) { - zip.push('-'); - zip = zip.concat(this.n(this.natural, 4, {max: 9})); - } - - return zip.join(""); - }; - - // -- End Location -- - - // -- Time - - Chance.prototype.ampm = function () { - return this.bool() ? 'am' : 'pm'; - }; - - Chance.prototype.date = function (options) { - var date_string, date; - - // If interval is specified we ignore preset - if(options && (options.min || options.max)) { - options = initOptions(options, { - american: true, - string: false - }); - var min = typeof options.min !== "undefined" ? options.min.getTime() : 1; - // 100,000,000 days measured relative to midnight at the beginning of 01 January, 1970 UTC. http://es5.github.io/#x15.9.1.1 - var max = typeof options.max !== "undefined" ? options.max.getTime() : 8640000000000000; - - date = new Date(this.integer({min: min, max: max})); - } else { - var m = this.month({raw: true}); - var daysInMonth = m.days; - - if(options && options.month) { - // Mod 12 to allow months outside range of 0-11 (not encouraged, but also not prevented). - daysInMonth = this.get('months')[((options.month % 12) + 12) % 12].days; - } - - options = initOptions(options, { - year: parseInt(this.year(), 10), - // Necessary to subtract 1 because Date() 0-indexes month but not day or year - // for some reason. - month: m.numeric - 1, - day: this.natural({min: 1, max: daysInMonth}), - hour: this.hour({twentyfour: true}), - minute: this.minute(), - second: this.second(), - millisecond: this.millisecond(), - american: true, - string: false - }); - - date = new Date(options.year, options.month, options.day, options.hour, options.minute, options.second, options.millisecond); - } - - if (options.american) { - // Adding 1 to the month is necessary because Date() 0-indexes - // months but not day for some odd reason. - date_string = (date.getMonth() + 1) + '/' + date.getDate() + '/' + date.getFullYear(); - } else { - date_string = date.getDate() + '/' + (date.getMonth() + 1) + '/' + date.getFullYear(); - } - - return options.string ? date_string : date; - }; - - Chance.prototype.hammertime = function (options) { - return this.date(options).getTime(); - }; - - Chance.prototype.hour = function (options) { - options = initOptions(options, { - min: options && options.twentyfour ? 0 : 1, - max: options && options.twentyfour ? 23 : 12 - }); - - testRange(options.min < 0, "Chance: Min cannot be less than 0."); - testRange(options.twentyfour && options.max > 23, "Chance: Max cannot be greater than 23 for twentyfour option."); - testRange(!options.twentyfour && options.max > 12, "Chance: Max cannot be greater than 12."); - testRange(options.min > options.max, "Chance: Min cannot be greater than Max."); - - return this.natural({min: options.min, max: options.max}); - }; - - Chance.prototype.millisecond = function () { - return this.natural({max: 999}); - }; - - Chance.prototype.minute = Chance.prototype.second = function (options) { - options = initOptions(options, {min: 0, max: 59}); - - testRange(options.min < 0, "Chance: Min cannot be less than 0."); - testRange(options.max > 59, "Chance: Max cannot be greater than 59."); - testRange(options.min > options.max, "Chance: Min cannot be greater than Max."); - - return this.natural({min: options.min, max: options.max}); - }; - - Chance.prototype.month = function (options) { - options = initOptions(options, {min: 1, max: 12}); - - testRange(options.min < 1, "Chance: Min cannot be less than 1."); - testRange(options.max > 12, "Chance: Max cannot be greater than 12."); - testRange(options.min > options.max, "Chance: Min cannot be greater than Max."); - - var month = this.pick(this.months().slice(options.min - 1, options.max)); - return options.raw ? month : month.name; - }; - - Chance.prototype.months = function () { - return this.get("months"); - }; - - Chance.prototype.second = function () { - return this.natural({max: 59}); - }; - - Chance.prototype.timestamp = function () { - return this.natural({min: 1, max: parseInt(new Date().getTime() / 1000, 10)}); - }; - - Chance.prototype.weekday = function (options) { - options = initOptions(options, {weekday_only: false}); - var weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]; - if (!options.weekday_only) { - weekdays.push("Saturday"); - weekdays.push("Sunday"); - } - return this.pickone(weekdays); - }; - - Chance.prototype.year = function (options) { - // Default to current year as min if none specified - options = initOptions(options, {min: new Date().getFullYear()}); - - // Default to one century after current year as max if none specified - options.max = (typeof options.max !== "undefined") ? options.max : options.min + 100; - - return this.natural(options).toString(); - }; - - // -- End Time - - // -- Finance -- - - Chance.prototype.cc = function (options) { - options = initOptions(options); - - var type, number, to_generate; - - type = (options.type) ? - this.cc_type({ name: options.type, raw: true }) : - this.cc_type({ raw: true }); - - number = type.prefix.split(""); - to_generate = type.length - type.prefix.length - 1; - - // Generates n - 1 digits - number = number.concat(this.n(this.integer, to_generate, {min: 0, max: 9})); - - // Generates the last digit according to Luhn algorithm - number.push(this.luhn_calculate(number.join(""))); - - return number.join(""); - }; - - Chance.prototype.cc_types = function () { - // http://en.wikipedia.org/wiki/Bank_card_number#Issuer_identification_number_.28IIN.29 - return this.get("cc_types"); - }; - - Chance.prototype.cc_type = function (options) { - options = initOptions(options); - var types = this.cc_types(), - type = null; - - if (options.name) { - for (var i = 0; i < types.length; i++) { - // Accept either name or short_name to specify card type - if (types[i].name === options.name || types[i].short_name === options.name) { - type = types[i]; - break; - } - } - if (type === null) { - throw new RangeError("Chance: Credit card type '" + options.name + "' is not supported"); - } - } else { - type = this.pick(types); - } - - return options.raw ? type : type.name; - }; - - // return all world currency by ISO 4217 - Chance.prototype.currency_types = function () { - return this.get("currency_types"); - }; - - // return random world currency by ISO 4217 - Chance.prototype.currency = function () { - return this.pick(this.currency_types()); - }; - - // return all timezones available - Chance.prototype.timezones = function () { - return this.get("timezones"); - }; - - // return random timezone - Chance.prototype.timezone = function () { - return this.pick(this.timezones()); - }; - - //Return random correct currency exchange pair (e.g. EUR/USD) or array of currency code - Chance.prototype.currency_pair = function (returnAsString) { - var currencies = this.unique(this.currency, 2, { - comparator: function(arr, val) { - - return arr.reduce(function(acc, item) { - // If a match has been found, short circuit check and just return - return acc || (item.code === val.code); - }, false); - } - }); - - if (returnAsString) { - return currencies[0].code + '/' + currencies[1].code; - } else { - return currencies; - } - }; - - Chance.prototype.dollar = function (options) { - // By default, a somewhat more sane max for dollar than all available numbers - options = initOptions(options, {max : 10000, min : 0}); - - var dollar = this.floating({min: options.min, max: options.max, fixed: 2}).toString(), - cents = dollar.split('.')[1]; - - if (cents === undefined) { - dollar += '.00'; - } else if (cents.length < 2) { - dollar = dollar + '0'; - } - - if (dollar < 0) { - return '-$' + dollar.replace('-', ''); - } else { - return '$' + dollar; - } - }; - - Chance.prototype.euro = function (options) { - return Number(this.dollar(options).replace("$", "")).toLocaleString() + "€"; - }; - - Chance.prototype.exp = function (options) { - options = initOptions(options); - var exp = {}; - - exp.year = this.exp_year(); - - // If the year is this year, need to ensure month is greater than the - // current month or this expiration will not be valid - if (exp.year === (new Date().getFullYear()).toString()) { - exp.month = this.exp_month({future: true}); - } else { - exp.month = this.exp_month(); - } - - return options.raw ? exp : exp.month + '/' + exp.year; - }; - - Chance.prototype.exp_month = function (options) { - options = initOptions(options); - var month, month_int, - // Date object months are 0 indexed - curMonth = new Date().getMonth() + 1; - - if (options.future && (curMonth !== 12)) { - do { - month = this.month({raw: true}).numeric; - month_int = parseInt(month, 10); - } while (month_int <= curMonth); - } else { - month = this.month({raw: true}).numeric; - } - - return month; - }; - - Chance.prototype.exp_year = function () { - var curMonth = new Date().getMonth() + 1, - curYear = new Date().getFullYear(); - - return this.year({min: ((curMonth === 12) ? (curYear + 1) : curYear), max: (curYear + 10)}); - }; - - Chance.prototype.vat = function (options) { - options = initOptions(options, { country: 'it' }); - switch (options.country.toLowerCase()) { - case 'it': - return this.it_vat(); - } - }; - - /** - * Generate a string matching IBAN pattern (https://en.wikipedia.org/wiki/International_Bank_Account_Number). - * No country-specific formats support (yet) - */ - Chance.prototype.iban = function () { - var alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - var alphanum = alpha + '0123456789'; - var iban = - this.string({ length: 2, pool: alpha }) + - this.pad(this.integer({ min: 0, max: 99 }), 2) + - this.string({ length: 4, pool: alphanum }) + - this.pad(this.natural(), this.natural({ min: 6, max: 26 })); - return iban; - }; - - // -- End Finance - - // -- Regional - - Chance.prototype.it_vat = function () { - var it_vat = this.natural({min: 1, max: 1800000}); - - it_vat = this.pad(it_vat, 7) + this.pad(this.pick(this.provinces({ country: 'it' })).code, 3); - return it_vat + this.luhn_calculate(it_vat); - }; - - /* - * this generator is written following the official algorithm - * all data can be passed explicitely or randomized by calling chance.cf() without options - * the code does not check that the input data is valid (it goes beyond the scope of the generator) - * - * @param [Object] options = { first: first name, - * last: last name, - * gender: female|male, - birthday: JavaScript date object, - city: string(4), 1 letter + 3 numbers - } - * @return [string] codice fiscale - * - */ - Chance.prototype.cf = function (options) { - options = options || {}; - var gender = !!options.gender ? options.gender : this.gender(), - first = !!options.first ? options.first : this.first( { gender: gender, nationality: 'it'} ), - last = !!options.last ? options.last : this.last( { nationality: 'it'} ), - birthday = !!options.birthday ? options.birthday : this.birthday(), - city = !!options.city ? options.city : this.pickone(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'L', 'M', 'Z']) + this.pad(this.natural({max:999}), 3), - cf = [], - name_generator = function(name, isLast) { - var temp, - return_value = []; - - if (name.length < 3) { - return_value = name.split("").concat("XXX".split("")).splice(0,3); - } - else { - temp = name.toUpperCase().split('').map(function(c){ - return ("BCDFGHJKLMNPRSTVWZ".indexOf(c) !== -1) ? c : undefined; - }).join(''); - if (temp.length > 3) { - if (isLast) { - temp = temp.substr(0,3); - } else { - temp = temp[0] + temp.substr(2,2); - } - } - if (temp.length < 3) { - return_value = temp; - temp = name.toUpperCase().split('').map(function(c){ - return ("AEIOU".indexOf(c) !== -1) ? c : undefined; - }).join('').substr(0, 3 - return_value.length); - } - return_value = return_value + temp; - } - - return return_value; - }, - date_generator = function(birthday, gender, that) { - var lettermonths = ['A', 'B', 'C', 'D', 'E', 'H', 'L', 'M', 'P', 'R', 'S', 'T']; - - return birthday.getFullYear().toString().substr(2) + - lettermonths[birthday.getMonth()] + - that.pad(birthday.getDate() + ((gender.toLowerCase() === "female") ? 40 : 0), 2); - }, - checkdigit_generator = function(cf) { - var range1 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", - range2 = "ABCDEFGHIJABCDEFGHIJKLMNOPQRSTUVWXYZ", - evens = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", - odds = "BAKPLCQDREVOSFTGUHMINJWZYX", - digit = 0; - - - for(var i = 0; i < 15; i++) { - if (i % 2 !== 0) { - digit += evens.indexOf(range2[range1.indexOf(cf[i])]); - } - else { - digit += odds.indexOf(range2[range1.indexOf(cf[i])]); - } - } - return evens[digit % 26]; - }; - - cf = cf.concat(name_generator(last, true), name_generator(first), date_generator(birthday, gender, this), city.toUpperCase().split("")).join(""); - cf += checkdigit_generator(cf.toUpperCase(), this); - - return cf.toUpperCase(); - }; - - Chance.prototype.pl_pesel = function () { - var number = this.natural({min: 1, max: 9999999999}); - var arr = this.pad(number, 10).split(''); - for (var i = 0; i < arr.length; i++) { - arr[i] = parseInt(arr[i]); - } - - var controlNumber = (1 * arr[0] + 3 * arr[1] + 7 * arr[2] + 9 * arr[3] + 1 * arr[4] + 3 * arr[5] + 7 * arr[6] + 9 * arr[7] + 1 * arr[8] + 3 * arr[9]) % 10; - if(controlNumber !== 0) { - controlNumber = 10 - controlNumber; - } - - return arr.join('') + controlNumber; - }; - - Chance.prototype.pl_nip = function () { - var number = this.natural({min: 1, max: 999999999}); - var arr = this.pad(number, 9).split(''); - for (var i = 0; i < arr.length; i++) { - arr[i] = parseInt(arr[i]); - } - - var controlNumber = (6 * arr[0] + 5 * arr[1] + 7 * arr[2] + 2 * arr[3] + 3 * arr[4] + 4 * arr[5] + 5 * arr[6] + 6 * arr[7] + 7 * arr[8]) % 11; - if(controlNumber === 10) { - return this.pl_nip(); - } - - return arr.join('') + controlNumber; - }; - - Chance.prototype.pl_regon = function () { - var number = this.natural({min: 1, max: 99999999}); - var arr = this.pad(number, 8).split(''); - for (var i = 0; i < arr.length; i++) { - arr[i] = parseInt(arr[i]); - } - - var controlNumber = (8 * arr[0] + 9 * arr[1] + 2 * arr[2] + 3 * arr[3] + 4 * arr[4] + 5 * arr[5] + 6 * arr[6] + 7 * arr[7]) % 11; - if(controlNumber === 10) { - controlNumber = 0; - } - - return arr.join('') + controlNumber; - }; - - // -- End Regional - - // -- Music -- - - // Genre choices: - // Rock,Pop,Hip-Hop,Jazz,Classical,Electronic,Country,R&B,Reggae, - // Blues,Metal,Folk,Alternative,Punk,Disco,Funk,Techno, - // Indie,Gospel,Dance,Children's,World - - Chance.prototype.music_genre = function (genre = 'general') { - if (!(genre.toLowerCase() in data.music_genres)) { - throw new Error(`Unsupported genre: ${genre}`); - } - - const genres = data.music_genres[genre.toLowerCase()]; - const randomIndex = this.integer({ min: 0, max: genres.length - 1 }); - - return genres[randomIndex]; - }; - - Chance.prototype.note = function(options) { - // choices for 'notes' option: - // flatKey - chromatic scale with flat notes (default) - // sharpKey - chromatic scale with sharp notes - // flats - just flat notes - // sharps - just sharp notes - // naturals - just natural notes - // all - naturals, sharps and flats - options = initOptions(options, { notes : 'flatKey'}); - var scales = { - naturals: ['C', 'D', 'E', 'F', 'G', 'A', 'B'], - flats: ['D♭', 'E♭', 'G♭', 'A♭', 'B♭'], - sharps: ['C♯', 'D♯', 'F♯', 'G♯', 'A♯'] - }; - scales.all = scales.naturals.concat(scales.flats.concat(scales.sharps)) - scales.flatKey = scales.naturals.concat(scales.flats) - scales.sharpKey = scales.naturals.concat(scales.sharps) - return this.pickone(scales[options.notes]); - } - - Chance.prototype.midi_note = function(options) { - var min = 0; - var max = 127; - options = initOptions(options, { min : min, max : max }); - return this.integer({min: options.min, max: options.max}); - } - - Chance.prototype.chord_quality = function(options) { - options = initOptions(options, { jazz: true }); - var chord_qualities = ['maj', 'min', 'aug', 'dim']; - if (options.jazz){ - chord_qualities = [ - 'maj7', - 'min7', - '7', - 'sus', - 'dim', - 'ø' - ]; - } - return this.pickone(chord_qualities); - } - - Chance.prototype.chord = function (options) { - options = initOptions(options); - return this.note(options) + this.chord_quality(options); - } - - Chance.prototype.tempo = function (options) { - var min = 40; - var max = 320; - options = initOptions(options, {min: min, max: max}); - return this.integer({min: options.min, max: options.max}); - } - - // -- End Music - - // -- Miscellaneous -- - - // Coin - Flip, flip, flipadelphia - Chance.prototype.coin = function() { - return this.bool() ? "heads" : "tails"; - } - - // Dice - For all the board game geeks out there, myself included ;) - function diceFn (range) { - return function () { - return this.natural(range); - }; - } - Chance.prototype.d4 = diceFn({min: 1, max: 4}); - Chance.prototype.d6 = diceFn({min: 1, max: 6}); - Chance.prototype.d8 = diceFn({min: 1, max: 8}); - Chance.prototype.d10 = diceFn({min: 1, max: 10}); - Chance.prototype.d12 = diceFn({min: 1, max: 12}); - Chance.prototype.d20 = diceFn({min: 1, max: 20}); - Chance.prototype.d30 = diceFn({min: 1, max: 30}); - Chance.prototype.d100 = diceFn({min: 1, max: 100}); - - Chance.prototype.rpg = function (thrown, options) { - options = initOptions(options); - if (!thrown) { - throw new RangeError("Chance: A type of die roll must be included"); - } else { - var bits = thrown.toLowerCase().split("d"), - rolls = []; - - if (bits.length !== 2 || !parseInt(bits[0], 10) || !parseInt(bits[1], 10)) { - throw new Error("Chance: Invalid format provided. Please provide #d# where the first # is the number of dice to roll, the second # is the max of each die"); - } - for (var i = bits[0]; i > 0; i--) { - rolls[i - 1] = this.natural({min: 1, max: bits[1]}); - } - return (typeof options.sum !== 'undefined' && options.sum) ? rolls.reduce(function (p, c) { return p + c; }) : rolls; - } - }; - - // Guid - Chance.prototype.guid = function (options) { - options = initOptions(options, { version: 5 }); - - var guid_pool = "abcdef1234567890", - variant_pool = "ab89", - guid = this.string({ pool: guid_pool, length: 8 }) + '-' + - this.string({ pool: guid_pool, length: 4 }) + '-' + - // The Version - options.version + - this.string({ pool: guid_pool, length: 3 }) + '-' + - // The Variant - this.string({ pool: variant_pool, length: 1 }) + - this.string({ pool: guid_pool, length: 3 }) + '-' + - this.string({ pool: guid_pool, length: 12 }); - return guid; - }; - - // Hash - Chance.prototype.hash = function (options) { - options = initOptions(options, {length : 40, casing: 'lower'}); - var pool = options.casing === 'upper' ? HEX_POOL.toUpperCase() : HEX_POOL; - return this.string({pool: pool, length: options.length}); - }; - - Chance.prototype.luhn_check = function (num) { - var str = num.toString(); - var checkDigit = +str.substring(str.length - 1); - return checkDigit === this.luhn_calculate(+str.substring(0, str.length - 1)); - }; - - Chance.prototype.luhn_calculate = function (num) { - var digits = num.toString().split("").reverse(); - var sum = 0; - var digit; - - for (var i = 0, l = digits.length; l > i; ++i) { - digit = +digits[i]; - if (i % 2 === 0) { - digit *= 2; - if (digit > 9) { - digit -= 9; - } - } - sum += digit; - } - return (sum * 9) % 10; - }; - - // MD5 Hash - Chance.prototype.md5 = function(options) { - var opts = { str: '', key: null, raw: false }; - - if (!options) { - opts.str = this.string(); - options = {}; - } - else if (typeof options === 'string') { - opts.str = options; - options = {}; - } - else if (typeof options !== 'object') { - return null; - } - else if(options.constructor === 'Array') { - return null; - } - - opts = initOptions(options, opts); - - if(!opts.str){ - throw new Error('A parameter is required to return an md5 hash.'); - } - - return this.bimd5.md5(opts.str, opts.key, opts.raw); - }; - - /** - * #Description: - * ===================================================== - * Generate random file name with extension - * - * The argument provide extension type - * -> raster - * -> vector - * -> 3d - * -> document - * - * If nothing is provided the function return random file name with random - * extension type of any kind - * - * The user can validate the file name length range - * If nothing provided the generated file name is random - * - * #Extension Pool : - * * Currently the supported extensions are - * -> some of the most popular raster image extensions - * -> some of the most popular vector image extensions - * -> some of the most popular 3d image extensions - * -> some of the most popular document extensions - * - * #Examples : - * ===================================================== - * - * Return random file name with random extension. The file extension - * is provided by a predefined collection of extensions. More about the extension - * pool can be found in #Extension Pool section - * - * chance.file() - * => dsfsdhjf.xml - * - * In order to generate a file name with specific length, specify the - * length property and integer value. The extension is going to be random - * - * chance.file({length : 10}) - * => asrtineqos.pdf - * - * In order to generate file with extension from some of the predefined groups - * of the extension pool just specify the extension pool category in fileType property - * - * chance.file({fileType : 'raster'}) - * => dshgssds.psd - * - * You can provide specific extension for your files - * chance.file({extension : 'html'}) - * => djfsd.html - * - * Or you could pass custom collection of extensions by array or by object - * chance.file({extensions : [...]}) - * => dhgsdsd.psd - * - * chance.file({extensions : { key : [...], key : [...]}}) - * => djsfksdjsd.xml - * - * @param [collection] options - * @return [string] - * - */ - Chance.prototype.file = function(options) { - - var fileOptions = options || {}; - var poolCollectionKey = "fileExtension"; - var typeRange = Object.keys(this.get("fileExtension"));//['raster', 'vector', '3d', 'document']; - var fileName; - var fileExtension; - - // Generate random file name - fileName = this.word({length : fileOptions.length}); - - // Generate file by specific extension provided by the user - if(fileOptions.extension) { - - fileExtension = fileOptions.extension; - return (fileName + '.' + fileExtension); - } - - // Generate file by specific extension collection - if(fileOptions.extensions) { - - if(Array.isArray(fileOptions.extensions)) { - - fileExtension = this.pickone(fileOptions.extensions); - return (fileName + '.' + fileExtension); - } - else if(fileOptions.extensions.constructor === Object) { - - var extensionObjectCollection = fileOptions.extensions; - var keys = Object.keys(extensionObjectCollection); - - fileExtension = this.pickone(extensionObjectCollection[this.pickone(keys)]); - return (fileName + '.' + fileExtension); - } - - throw new Error("Chance: Extensions must be an Array or Object"); - } - - // Generate file extension based on specific file type - if(fileOptions.fileType) { - - var fileType = fileOptions.fileType; - if(typeRange.indexOf(fileType) !== -1) { - - fileExtension = this.pickone(this.get(poolCollectionKey)[fileType]); - return (fileName + '.' + fileExtension); - } - - throw new RangeError("Chance: Expect file type value to be 'raster', 'vector', '3d' or 'document'"); - } - - // Generate random file name if no extension options are passed - fileExtension = this.pickone(this.get(poolCollectionKey)[this.pickone(typeRange)]); - return (fileName + '.' + fileExtension); - }; - - /** - * Generates file data of random bytes using the chance.file method for the file name - * - * @param {object} - * fileName: String - * fileExtention: String - * fileSize: Number <- in bytes - * @returns {object} fileName: String, fileData: Buffer - */ - Chance.prototype.fileWithContent = function (options){ - var fileOptions = options || {}; - var fileName = 'fileName' in fileOptions ? fileOptions.fileName : this.file().split(".")[0]; - fileName += "." + ('fileExtension' in fileOptions ? fileOptions.fileExtension : this.file().split(".")[1]); - - - if (typeof fileOptions.fileSize !== "number") { - throw new Error('File size must be an integer') - } - var file = { - fileData: this.buffer({length: fileOptions.fileSize}), - fileName: fileName, - }; - return file; - } - - var data = { - - firstNames: { - "male": { - "en": ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Charles", "Thomas", "Christopher", "Daniel", "Matthew", "George", "Donald", "Anthony", "Paul", "Mark", "Edward", "Steven", "Kenneth", "Andrew", "Brian", "Joshua", "Kevin", "Ronald", "Timothy", "Jason", "Jeffrey", "Frank", "Gary", "Ryan", "Nicholas", "Eric", "Stephen", "Jacob", "Larry", "Jonathan", "Scott", "Raymond", "Justin", "Brandon", "Gregory", "Samuel", "Benjamin", "Patrick", "Jack", "Henry", "Walter", "Dennis", "Jerry", "Alexander", "Peter", "Tyler", "Douglas", "Harold", "Aaron", "Jose", "Adam", "Arthur", "Zachary", "Carl", "Nathan", "Albert", "Kyle", "Lawrence", "Joe", "Willie", "Gerald", "Roger", "Keith", "Jeremy", "Terry", "Harry", "Ralph", "Sean", "Jesse", "Roy", "Louis", "Billy", "Austin", "Bruce", "Eugene", "Christian", "Bryan", "Wayne", "Russell", "Howard", "Fred", "Ethan", "Jordan", "Philip", "Alan", "Juan", "Randy", "Vincent", "Bobby", "Dylan", "Johnny", "Phillip", "Victor", "Clarence", "Ernest", "Martin", "Craig", "Stanley", "Shawn", "Travis", "Bradley", "Leonard", "Earl", "Gabriel", "Jimmy", "Francis", "Todd", "Noah", "Danny", "Dale", "Cody", "Carlos", "Allen", "Frederick", "Logan", "Curtis", "Alex", "Joel", "Luis", "Norman", "Marvin", "Glenn", "Tony", "Nathaniel", "Rodney", "Melvin", "Alfred", "Steve", "Cameron", "Chad", "Edwin", "Caleb", "Evan", "Antonio", "Lee", "Herbert", "Jeffery", "Isaac", "Derek", "Ricky", "Marcus", "Theodore", "Elijah", "Luke", "Jesus", "Eddie", "Troy", "Mike", "Dustin", "Ray", "Adrian", "Bernard", "Leroy", "Angel", "Randall", "Wesley", "Ian", "Jared", "Mason", "Hunter", "Calvin", "Oscar", "Clifford", "Jay", "Shane", "Ronnie", "Barry", "Lucas", "Corey", "Manuel", "Leo", "Tommy", "Warren", "Jackson", "Isaiah", "Connor", "Don", "Dean", "Jon", "Julian", "Miguel", "Bill", "Lloyd", "Charlie", "Mitchell", "Leon", "Jerome", "Darrell", "Jeremiah", "Alvin", "Brett", "Seth", "Floyd", "Jim", "Blake", "Micheal", "Gordon", "Trevor", "Lewis", "Erik", "Edgar", "Vernon", "Devin", "Gavin", "Jayden", "Chris", "Clyde", "Tom", "Derrick", "Mario", "Brent", "Marc", "Herman", "Chase", "Dominic", "Ricardo", "Franklin", "Maurice", "Max", "Aiden", "Owen", "Lester", "Gilbert", "Elmer", "Gene", "Francisco", "Glen", "Cory", "Garrett", "Clayton", "Sam", "Jorge", "Chester", "Alejandro", "Jeff", "Harvey", "Milton", "Cole", "Ivan", "Andre", "Duane", "Landon"], - // Data taken from http://www.dati.gov.it/dataset/comune-di-firenze_0163 - "it": ["Adolfo", "Alberto", "Aldo", "Alessandro", "Alessio", "Alfredo", "Alvaro", "Andrea", "Angelo", "Angiolo", "Antonino", "Antonio", "Attilio", "Benito", "Bernardo", "Bruno", "Carlo", "Cesare", "Christian", "Claudio", "Corrado", "Cosimo", "Cristian", "Cristiano", "Daniele", "Dario", "David", "Davide", "Diego", "Dino", "Domenico", "Duccio", "Edoardo", "Elia", "Elio", "Emanuele", "Emiliano", "Emilio", "Enrico", "Enzo", "Ettore", "Fabio", "Fabrizio", "Federico", "Ferdinando", "Fernando", "Filippo", "Francesco", "Franco", "Gabriele", "Giacomo", "Giampaolo", "Giampiero", "Giancarlo", "Gianfranco", "Gianluca", "Gianmarco", "Gianni", "Gino", "Giorgio", "Giovanni", "Giuliano", "Giulio", "Giuseppe", "Graziano", "Gregorio", "Guido", "Iacopo", "Jacopo", "Lapo", "Leonardo", "Lorenzo", "Luca", "Luciano", "Luigi", "Manuel", "Marcello", "Marco", "Marino", "Mario", "Massimiliano", "Massimo", "Matteo", "Mattia", "Maurizio", "Mauro", "Michele", "Mirko", "Mohamed", "Nello", "Neri", "Niccolò", "Nicola", "Osvaldo", "Otello", "Paolo", "Pier Luigi", "Piero", "Pietro", "Raffaele", "Remo", "Renato", "Renzo", "Riccardo", "Roberto", "Rolando", "Romano", "Salvatore", "Samuele", "Sandro", "Sergio", "Silvano", "Simone", "Stefano", "Thomas", "Tommaso", "Ubaldo", "Ugo", "Umberto", "Valerio", "Valter", "Vasco", "Vincenzo", "Vittorio"], - // Data taken from http://www.svbkindernamen.nl/int/nl/kindernamen/index.html - "nl": ["Aaron","Abel","Adam","Adriaan","Albert","Alexander","Ali","Arjen","Arno","Bart","Bas","Bastiaan","Benjamin","Bob", "Boris","Bram","Brent","Cas","Casper","Chris","Christiaan","Cornelis","Daan","Daley","Damian","Dani","Daniel","Daniël","David","Dean","Dirk","Dylan","Egbert","Elijah","Erik","Erwin","Evert","Ezra","Fabian","Fedde","Finn","Florian","Floris","Frank","Frans","Frederik","Freek","Geert","Gerard","Gerben","Gerrit","Gijs","Guus","Hans","Hendrik","Henk","Herman","Hidde","Hugo","Jaap","Jan Jaap","Jan-Willem","Jack","Jacob","Jan","Jason","Jasper","Jayden","Jelle","Jelte","Jens","Jeroen","Jesse","Jim","Job","Joep","Johannes","John","Jonathan","Joris","Joshua","Joël","Julian","Kees","Kevin","Koen","Lars","Laurens","Leendert","Lennard","Lodewijk","Luc","Luca","Lucas","Lukas","Luuk","Maarten","Marcus","Martijn","Martin","Matthijs","Maurits","Max","Mees","Melle","Mick","Mika","Milan","Mohamed","Mohammed","Morris","Muhammed","Nathan","Nick","Nico","Niek","Niels","Noah","Noud","Olivier","Oscar","Owen","Paul","Pepijn","Peter","Pieter","Pim","Quinten","Reinier","Rens","Robin","Ruben","Sam","Samuel","Sander","Sebastiaan","Sem","Sep","Sepp","Siem","Simon","Stan","Stef","Steven","Stijn","Sven","Teun","Thijmen","Thijs","Thomas","Tijn","Tim","Timo","Tobias","Tom","Victor","Vince","Willem","Wim","Wouter","Yusuf"], - // Data taken from https://fr.wikipedia.org/wiki/Liste_de_pr%C3%A9noms_fran%C3%A7ais_et_de_la_francophonie - "fr": ["Aaron","Abdon","Abel","Abélard","Abelin","Abondance","Abraham","Absalon","Acace","Achaire","Achille","Adalard","Adalbald","Adalbéron","Adalbert","Adalric","Adam","Adegrin","Adel","Adelin","Andelin","Adelphe","Adam","Adéodat","Adhémar","Adjutor","Adolphe","Adonis","Adon","Adrien","Agapet","Agathange","Agathon","Agilbert","Agénor","Agnan","Aignan","Agrippin","Aimable","Aimé","Alain","Alban","Albin","Aubin","Albéric","Albert","Albertet","Alcibiade","Alcide","Alcée","Alcime","Aldonce","Aldric","Aldéric","Aleaume","Alexandre","Alexis","Alix","Alliaume","Aleaume","Almine","Almire","Aloïs","Alphée","Alphonse","Alpinien","Alverède","Amalric","Amaury","Amandin","Amant","Ambroise","Amédée","Amélien","Amiel","Amour","Anaël","Anastase","Anatole","Ancelin","Andéol","Andoche","André","Andoche","Ange","Angelin","Angilbe","Anglebert","Angoustan","Anicet","Anne","Annibal","Ansbert","Anselme","Anthelme","Antheaume","Anthime","Antide","Antoine","Antonius","Antonin","Apollinaire","Apollon","Aquilin","Arcade","Archambaud","Archambeau","Archange","Archibald","Arian","Ariel","Ariste","Aristide","Armand","Armel","Armin","Arnould","Arnaud","Arolde","Arsène","Arsinoé","Arthaud","Arthème","Arthur","Ascelin","Athanase","Aubry","Audebert","Audouin","Audran","Audric","Auguste","Augustin","Aurèle","Aurélien","Aurian","Auxence","Axel","Aymard","Aymeric","Aymon","Aymond","Balthazar","Baptiste","Barnabé","Barthélemy","Bartimée","Basile","Bastien","Baudouin","Bénigne","Benjamin","Benoît","Bérenger","Bérard","Bernard","Bertrand","Blaise","Bon","Boniface","Bouchard","Brice","Brieuc","Bruno","Brunon","Calixte","Calliste","Camélien","Camille","Camillien","Candide","Caribert","Carloman","Cassandre","Cassien","Cédric","Céleste","Célestin","Célien","Césaire","César","Charles","Charlemagne","Childebert","Chilpéric","Chrétien","Christian","Christodule","Christophe","Chrysostome","Clarence","Claude","Claudien","Cléandre","Clément","Clotaire","Côme","Constance","Constant","Constantin","Corentin","Cyprien","Cyriaque","Cyrille","Cyril","Damien","Daniel","David","Delphin","Denis","Désiré","Didier","Dieudonné","Dimitri","Dominique","Dorian","Dorothée","Edgard","Edmond","Édouard","Éleuthère","Élie","Élisée","Émeric","Émile","Émilien","Emmanuel","Enguerrand","Épiphane","Éric","Esprit","Ernest","Étienne","Eubert","Eudes","Eudoxe","Eugène","Eusèbe","Eustache","Évariste","Évrard","Fabien","Fabrice","Falba","Félicité","Félix","Ferdinand","Fiacre","Fidèle","Firmin","Flavien","Flodoard","Florent","Florentin","Florestan","Florian","Fortuné","Foulques","Francisque","François","Français","Franciscus","Francs","Frédéric","Fulbert","Fulcran","Fulgence","Gabin","Gabriel","Gaël","Garnier","Gaston","Gaspard","Gatien","Gaud","Gautier","Gédéon","Geoffroy","Georges","Géraud","Gérard","Gerbert","Germain","Gervais","Ghislain","Gilbert","Gilles","Girart","Gislebert","Gondebaud","Gonthier","Gontran","Gonzague","Grégoire","Guérin","Gui","Guillaume","Gustave","Guy","Guyot","Hardouin","Hector","Hédelin","Hélier","Henri","Herbert","Herluin","Hervé","Hilaire","Hildebert","Hincmar","Hippolyte","Honoré","Hubert","Hugues","Innocent","Isabeau","Isidore","Jacques","Japhet","Jason","Jean","Jeannel","Jeannot","Jérémie","Jérôme","Joachim","Joanny","Job","Jocelyn","Joël","Johan","Jonas","Jonathan","Joseph","Josse","Josselin","Jourdain","Jude","Judicaël","Jules","Julien","Juste","Justin","Lambert","Landry","Laurent","Lazare","Léandre","Léon","Léonard","Léopold","Leu","Loup","Leufroy","Libère","Liétald","Lionel","Loïc","Longin","Lorrain","Lorraine","Lothaire","Louis","Loup","Luc","Lucas","Lucien","Ludolphe","Ludovic","Macaire","Malo","Mamert","Manassé","Marc","Marceau","Marcel","Marcelin","Marius","Marseille","Martial","Martin","Mathurin","Matthias","Mathias","Matthieu","Maugis","Maurice","Mauricet","Maxence","Maxime","Maximilien","Mayeul","Médéric","Melchior","Mence","Merlin","Mérovée","Michaël","Michel","Moïse","Morgan","Nathan","Nathanaël","Narcisse","Néhémie","Nestor","Nestor","Nicéphore","Nicolas","Noé","Noël","Norbert","Normand","Normands","Octave","Odilon","Odon","Oger","Olivier","Oury","Pacôme","Palémon","Parfait","Pascal","Paterne","Patrice","Paul","Pépin","Perceval","Philémon","Philibert","Philippe","Philothée","Pie","Pierre","Pierrick","Prosper","Quentin","Raoul","Raphaël","Raymond","Régis","Réjean","Rémi","Renaud","René","Reybaud","Richard","Robert","Roch","Rodolphe","Rodrigue","Roger","Roland","Romain","Romuald","Roméo","Rome","Ronan","Roselin","Salomon","Samuel","Savin","Savinien","Scholastique","Sébastien","Séraphin","Serge","Séverin","Sidoine","Sigebert","Sigismond","Silvère","Simon","Siméon","Sixte","Stanislas","Stéphane","Stephan","Sylvain","Sylvestre","Tancrède","Tanguy","Taurin","Théodore","Théodose","Théophile","Théophraste","Thibault","Thibert","Thierry","Thomas","Timoléon","Timothée","Titien","Tonnin","Toussaint","Trajan","Tristan","Turold","Tim","Ulysse","Urbain","Valentin","Valère","Valéry","Venance","Venant","Venceslas","Vianney","Victor","Victorien","Victorin","Vigile","Vincent","Vital","Vitalien","Vivien","Waleran","Wandrille","Xavier","Xénophon","Yves","Zacharie","Zaché","Zéphirin"] - }, - - "female": { - "en": ["Mary", "Emma", "Elizabeth", "Minnie", "Margaret", "Ida", "Alice", "Bertha", "Sarah", "Annie", "Clara", "Ella", "Florence", "Cora", "Martha", "Laura", "Nellie", "Grace", "Carrie", "Maude", "Mabel", "Bessie", "Jennie", "Gertrude", "Julia", "Hattie", "Edith", "Mattie", "Rose", "Catherine", "Lillian", "Ada", "Lillie", "Helen", "Jessie", "Louise", "Ethel", "Lula", "Myrtle", "Eva", "Frances", "Lena", "Lucy", "Edna", "Maggie", "Pearl", "Daisy", "Fannie", "Josephine", "Dora", "Rosa", "Katherine", "Agnes", "Marie", "Nora", "May", "Mamie", "Blanche", "Stella", "Ellen", "Nancy", "Effie", "Sallie", "Nettie", "Della", "Lizzie", "Flora", "Susie", "Maud", "Mae", "Etta", "Harriet", "Sadie", "Caroline", "Katie", "Lydia", "Elsie", "Kate", "Susan", "Mollie", "Alma", "Addie", "Georgia", "Eliza", "Lulu", "Nannie", "Lottie", "Amanda", "Belle", "Charlotte", "Rebecca", "Ruth", "Viola", "Olive", "Amelia", "Hannah", "Jane", "Virginia", "Emily", "Matilda", "Irene", "Kathryn", "Esther", "Willie", "Henrietta", "Ollie", "Amy", "Rachel", "Sara", "Estella", "Theresa", "Augusta", "Ora", "Pauline", "Josie", "Lola", "Sophia", "Leona", "Anne", "Mildred", "Ann", "Beulah", "Callie", "Lou", "Delia", "Eleanor", "Barbara", "Iva", "Louisa", "Maria", "Mayme", "Evelyn", "Estelle", "Nina", "Betty", "Marion", "Bettie", "Dorothy", "Luella", "Inez", "Lela", "Rosie", "Allie", "Millie", "Janie", "Cornelia", "Victoria", "Ruby", "Winifred", "Alta", "Celia", "Christine", "Beatrice", "Birdie", "Harriett", "Mable", "Myra", "Sophie", "Tillie", "Isabel", "Sylvia", "Carolyn", "Isabelle", "Leila", "Sally", "Ina", "Essie", "Bertie", "Nell", "Alberta", "Katharine", "Lora", "Rena", "Mina", "Rhoda", "Mathilda", "Abbie", "Eula", "Dollie", "Hettie", "Eunice", "Fanny", "Ola", "Lenora", "Adelaide", "Christina", "Lelia", "Nelle", "Sue", "Johanna", "Lilly", "Lucinda", "Minerva", "Lettie", "Roxie", "Cynthia", "Helena", "Hilda", "Hulda", "Bernice", "Genevieve", "Jean", "Cordelia", "Marian", "Francis", "Jeanette", "Adeline", "Gussie", "Leah", "Lois", "Lura", "Mittie", "Hallie", "Isabella", "Olga", "Phoebe", "Teresa", "Hester", "Lida", "Lina", "Winnie", "Claudia", "Marguerite", "Vera", "Cecelia", "Bess", "Emilie", "Rosetta", "Verna", "Myrtie", "Cecilia", "Elva", "Olivia", "Ophelia", "Georgie", "Elnora", "Violet", "Adele", "Lily", "Linnie", "Loretta", "Madge", "Polly", "Virgie", "Eugenia", "Lucile", "Lucille", "Mabelle", "Rosalie"], - // Data taken from http://www.dati.gov.it/dataset/comune-di-firenze_0162 - "it": ["Ada", "Adriana", "Alessandra", "Alessia", "Alice", "Angela", "Anna", "Anna Maria", "Annalisa", "Annita", "Annunziata", "Antonella", "Arianna", "Asia", "Assunta", "Aurora", "Barbara", "Beatrice", "Benedetta", "Bianca", "Bruna", "Camilla", "Carla", "Carlotta", "Carmela", "Carolina", "Caterina", "Catia", "Cecilia", "Chiara", "Cinzia", "Clara", "Claudia", "Costanza", "Cristina", "Daniela", "Debora", "Diletta", "Dina", "Donatella", "Elena", "Eleonora", "Elisa", "Elisabetta", "Emanuela", "Emma", "Eva", "Federica", "Fernanda", "Fiorella", "Fiorenza", "Flora", "Franca", "Francesca", "Gabriella", "Gaia", "Gemma", "Giada", "Gianna", "Gina", "Ginevra", "Giorgia", "Giovanna", "Giulia", "Giuliana", "Giuseppa", "Giuseppina", "Grazia", "Graziella", "Greta", "Ida", "Ilaria", "Ines", "Iolanda", "Irene", "Irma", "Isabella", "Jessica", "Laura", "Lea", "Letizia", "Licia", "Lidia", "Liliana", "Lina", "Linda", "Lisa", "Livia", "Loretta", "Luana", "Lucia", "Luciana", "Lucrezia", "Luisa", "Manuela", "Mara", "Marcella", "Margherita", "Maria", "Maria Cristina", "Maria Grazia", "Maria Luisa", "Maria Pia", "Maria Teresa", "Marina", "Marisa", "Marta", "Martina", "Marzia", "Matilde", "Melissa", "Michela", "Milena", "Mirella", "Monica", "Natalina", "Nella", "Nicoletta", "Noemi", "Olga", "Paola", "Patrizia", "Piera", "Pierina", "Raffaella", "Rebecca", "Renata", "Rina", "Rita", "Roberta", "Rosa", "Rosanna", "Rossana", "Rossella", "Sabrina", "Sandra", "Sara", "Serena", "Silvana", "Silvia", "Simona", "Simonetta", "Sofia", "Sonia", "Stefania", "Susanna", "Teresa", "Tina", "Tiziana", "Tosca", "Valentina", "Valeria", "Vanda", "Vanessa", "Vanna", "Vera", "Veronica", "Vilma", "Viola", "Virginia", "Vittoria"], - // Data taken from http://www.svbkindernamen.nl/int/nl/kindernamen/index.html - "nl": ["Ada", "Arianne", "Afke", "Amanda", "Amber", "Amy", "Aniek", "Anita", "Anja", "Anna", "Anne", "Annelies", "Annemarie", "Annette", "Anouk", "Astrid", "Aukje", "Barbara", "Bianca", "Carla", "Carlijn", "Carolien", "Chantal", "Charlotte", "Claudia", "Daniëlle", "Debora", "Diane", "Dora", "Eline", "Elise", "Ella", "Ellen", "Emma", "Esmee", "Evelien", "Esther", "Erica", "Eva", "Femke", "Fleur", "Floor", "Froukje", "Gea", "Gerda", "Hanna", "Hanneke", "Heleen", "Hilde", "Ilona", "Ina", "Inge", "Ingrid", "Iris", "Isabel", "Isabelle", "Janneke", "Jasmijn", "Jeanine", "Jennifer", "Jessica", "Johanna", "Joke", "Julia", "Julie", "Karen", "Karin", "Katja", "Kim", "Lara", "Laura", "Lena", "Lianne", "Lieke", "Lilian", "Linda", "Lisa", "Lisanne", "Lotte", "Louise", "Maaike", "Manon", "Marga", "Maria", "Marissa", "Marit", "Marjolein", "Martine", "Marleen", "Melissa", "Merel", "Miranda", "Michelle", "Mirjam", "Mirthe", "Naomi", "Natalie", 'Nienke', "Nina", "Noortje", "Olivia", "Patricia", "Paula", "Paulien", "Ramona", "Ria", "Rianne", "Roos", "Rosanne", "Ruth", "Sabrina", "Sandra", "Sanne", "Sara", "Saskia", "Silvia", "Sofia", "Sophie", "Sonja", "Suzanne", "Tamara", "Tess", "Tessa", "Tineke", "Valerie", "Vanessa", "Veerle", "Vera", "Victoria", "Wendy", "Willeke", "Yvonne", "Zoë"], - // Data taken from https://fr.wikipedia.org/wiki/Liste_de_pr%C3%A9noms_fran%C3%A7ais_et_de_la_francophonie - "fr": ["Abdon","Abel","Abigaëlle","Abigaïl","Acacius","Acanthe","Adalbert","Adalsinde","Adegrine","Adélaïde","Adèle","Adélie","Adeline","Adeltrude","Adolphe","Adonis","Adrastée","Adrehilde","Adrienne","Agathe","Agilbert","Aglaé","Aignan","Agneflète","Agnès","Agrippine","Aimé","Alaine","Alaïs","Albane","Albérade","Alberte","Alcide","Alcine","Alcyone","Aldegonde","Aleth","Alexandrine","Alexine","Alice","Aliénor","Aliette","Aline","Alix","Alizé","Aloïse","Aloyse","Alphonsine","Althée","Amaliane","Amalthée","Amande","Amandine","Amant","Amarande","Amaranthe","Amaryllis","Ambre","Ambroisie","Amélie","Améthyste","Aminte","Anaël","Anaïs","Anastasie","Anatole","Ancelin","Andrée","Anémone","Angadrême","Angèle","Angeline","Angélique","Angilbert","Anicet","Annabelle","Anne","Annette","Annick","Annie","Annonciade","Ansbert","Anstrudie","Anthelme","Antigone","Antoinette","Antonine","Aphélie","Apolline","Apollonie","Aquiline","Arabelle","Arcadie","Archange","Argine","Ariane","Aricie","Ariel","Arielle","Arlette","Armance","Armande","Armandine","Armelle","Armide","Armelle","Armin","Arnaud","Arsène","Arsinoé","Artémis","Arthur","Ascelin","Ascension","Assomption","Astarté","Astérie","Astrée","Astrid","Athalie","Athanasie","Athina","Aube","Albert","Aude","Audrey","Augustine","Aure","Aurélie","Aurélien","Aurèle","Aurore","Auxence","Aveline","Abigaëlle","Avoye","Axelle","Aymard","Azalée","Adèle","Adeline","Barbe","Basilisse","Bathilde","Béatrice","Béatrix","Bénédicte","Bérengère","Bernadette","Berthe","Bertille","Beuve","Blanche","Blanc","Blandine","Brigitte","Brune","Brunehilde","Callista","Camille","Capucine","Carine","Caroline","Cassandre","Catherine","Cécile","Céleste","Célestine","Céline","Chantal","Charlène","Charline","Charlotte","Chloé","Christelle","Christiane","Christine","Claire","Clara","Claude","Claudine","Clarisse","Clémence","Clémentine","Cléo","Clio","Clotilde","Coline","Conception","Constance","Coralie","Coraline","Corentine","Corinne","Cyrielle","Daniel","Daniel","Daphné","Débora","Delphine","Denise","Diane","Dieudonné","Dominique","Doriane","Dorothée","Douce","Édith","Edmée","Éléonore","Éliane","Élia","Éliette","Élisabeth","Élise","Ella","Élodie","Éloïse","Elsa","Émeline","Émérance","Émérentienne","Émérencie","Émilie","Emma","Emmanuelle","Emmelie","Ernestine","Esther","Estelle","Eudoxie","Eugénie","Eulalie","Euphrasie","Eusébie","Évangéline","Eva","Ève","Évelyne","Fanny","Fantine","Faustine","Félicie","Fernande","Flavie","Fleur","Flore","Florence","Florie","Fortuné","France","Francia","Françoise","Francine","Gabrielle","Gaëlle","Garance","Geneviève","Georgette","Gerberge","Germaine","Gertrude","Gisèle","Guenièvre","Guilhemine","Guillemette","Gustave","Gwenael","Hélène","Héloïse","Henriette","Hermine","Hermione","Hippolyte","Honorine","Hortense","Huguette","Ines","Irène","Irina","Iris","Isabeau","Isabelle","Iseult","Isolde","Ismérie","Jacinthe","Jacqueline","Jade","Janine","Jeanne","Jocelyne","Joëlle","Joséphine","Judith","Julia","Julie","Jules","Juliette","Justine","Katy","Kathy","Katie","Laura","Laure","Laureline","Laurence","Laurene","Lauriane","Laurianne","Laurine","Léa","Léna","Léonie","Léon","Léontine","Lorraine","Lucie","Lucienne","Lucille","Ludivine","Lydie","Lydie","Megane","Madeleine","Magali","Maguelone","Mallaury","Manon","Marceline","Margot","Marguerite","Marianne","Marie","Myriam","Marie","Marine","Marion","Marlène","Marthe","Martine","Mathilde","Maud","Maureen","Mauricette","Maxime","Mélanie","Melissa","Mélissandre","Mélisande","Mélodie","Michel","Micheline","Mireille","Miriam","Moïse","Monique","Morgane","Muriel","Mylène","Nadège","Nadine","Nathalie","Nicole","Nicolette","Nine","Noël","Noémie","Océane","Odette","Odile","Olive","Olivia","Olympe","Ombline","Ombeline","Ophélie","Oriande","Oriane","Ozanne","Pascale","Pascaline","Paule","Paulette","Pauline","Priscille","Prisca","Prisque","Pécine","Pélagie","Pénélope","Perrine","Pétronille","Philippine","Philomène","Philothée","Primerose","Prudence","Pulchérie","Quentine","Quiéta","Quintia","Quintilla","Rachel","Raphaëlle","Raymonde","Rebecca","Régine","Réjeanne","René","Rita","Rita","Rolande","Romane","Rosalie","Rose","Roseline","Sabine","Salomé","Sandra","Sandrine","Sarah","Ségolène","Séverine","Sibylle","Simone","Sixt","Solange","Soline","Solène","Sophie","Stéphanie","Suzanne","Sylvain","Sylvie","Tatiana","Thaïs","Théodora","Thérèse","Tiphaine","Ursule","Valentine","Valérie","Véronique","Victoire","Victorine","Vinciane","Violette","Virginie","Viviane","Xavière","Yolande","Ysaline","Yvette","Yvonne","Zélie","Zita","Zoé"] - } - }, - - lastNames: { - "en": ['Smith', 'Johnson', 'Williams', 'Jones', 'Brown', 'Davis', 'Miller', 'Wilson', 'Moore', 'Taylor', 'Anderson', 'Thomas', 'Jackson', 'White', 'Harris', 'Martin', 'Thompson', 'Garcia', 'Martinez', 'Robinson', 'Clark', 'Rodriguez', 'Lewis', 'Lee', 'Walker', 'Hall', 'Allen', 'Young', 'Hernandez', 'King', 'Wright', 'Lopez', 'Hill', 'Scott', 'Green', 'Adams', 'Baker', 'Gonzalez', 'Nelson', 'Carter', 'Mitchell', 'Perez', 'Roberts', 'Turner', 'Phillips', 'Campbell', 'Parker', 'Evans', 'Edwards', 'Collins', 'Stewart', 'Sanchez', 'Morris', 'Rogers', 'Reed', 'Cook', 'Morgan', 'Bell', 'Murphy', 'Bailey', 'Rivera', 'Cooper', 'Richardson', 'Cox', 'Howard', 'Ward', 'Torres', 'Peterson', 'Gray', 'Ramirez', 'James', 'Watson', 'Brooks', 'Kelly', 'Sanders', 'Price', 'Bennett', 'Wood', 'Barnes', 'Ross', 'Henderson', 'Coleman', 'Jenkins', 'Perry', 'Powell', 'Long', 'Patterson', 'Hughes', 'Flores', 'Washington', 'Butler', 'Simmons', 'Foster', 'Gonzales', 'Bryant', 'Alexander', 'Russell', 'Griffin', 'Diaz', 'Hayes', 'Myers', 'Ford', 'Hamilton', 'Graham', 'Sullivan', 'Wallace', 'Woods', 'Cole', 'West', 'Jordan', 'Owens', 'Reynolds', 'Fisher', 'Ellis', 'Harrison', 'Gibson', 'McDonald', 'Cruz', 'Marshall', 'Ortiz', 'Gomez', 'Murray', 'Freeman', 'Wells', 'Webb', 'Simpson', 'Stevens', 'Tucker', 'Porter', 'Hunter', 'Hicks', 'Crawford', 'Henry', 'Boyd', 'Mason', 'Morales', 'Kennedy', 'Warren', 'Dixon', 'Ramos', 'Reyes', 'Burns', 'Gordon', 'Shaw', 'Holmes', 'Rice', 'Robertson', 'Hunt', 'Black', 'Daniels', 'Palmer', 'Mills', 'Nichols', 'Grant', 'Knight', 'Ferguson', 'Rose', 'Stone', 'Hawkins', 'Dunn', 'Perkins', 'Hudson', 'Spencer', 'Gardner', 'Stephens', 'Payne', 'Pierce', 'Berry', 'Matthews', 'Arnold', 'Wagner', 'Willis', 'Ray', 'Watkins', 'Olson', 'Carroll', 'Duncan', 'Snyder', 'Hart', 'Cunningham', 'Bradley', 'Lane', 'Andrews', 'Ruiz', 'Harper', 'Fox', 'Riley', 'Armstrong', 'Carpenter', 'Weaver', 'Greene', 'Lawrence', 'Elliott', 'Chavez', 'Sims', 'Austin', 'Peters', 'Kelley', 'Franklin', 'Lawson', 'Fields', 'Gutierrez', 'Ryan', 'Schmidt', 'Carr', 'Vasquez', 'Castillo', 'Wheeler', 'Chapman', 'Oliver', 'Montgomery', 'Richards', 'Williamson', 'Johnston', 'Banks', 'Meyer', 'Bishop', 'McCoy', 'Howell', 'Alvarez', 'Morrison', 'Hansen', 'Fernandez', 'Garza', 'Harvey', 'Little', 'Burton', 'Stanley', 'Nguyen', 'George', 'Jacobs', 'Reid', 'Kim', 'Fuller', 'Lynch', 'Dean', 'Gilbert', 'Garrett', 'Romero', 'Welch', 'Larson', 'Frazier', 'Burke', 'Hanson', 'Day', 'Mendoza', 'Moreno', 'Bowman', 'Medina', 'Fowler', 'Brewer', 'Hoffman', 'Carlson', 'Silva', 'Pearson', 'Holland', 'Douglas', 'Fleming', 'Jensen', 'Vargas', 'Byrd', 'Davidson', 'Hopkins', 'May', 'Terry', 'Herrera', 'Wade', 'Soto', 'Walters', 'Curtis', 'Neal', 'Caldwell', 'Lowe', 'Jennings', 'Barnett', 'Graves', 'Jimenez', 'Horton', 'Shelton', 'Barrett', 'Obrien', 'Castro', 'Sutton', 'Gregory', 'McKinney', 'Lucas', 'Miles', 'Craig', 'Rodriquez', 'Chambers', 'Holt', 'Lambert', 'Fletcher', 'Watts', 'Bates', 'Hale', 'Rhodes', 'Pena', 'Beck', 'Newman', 'Haynes', 'McDaniel', 'Mendez', 'Bush', 'Vaughn', 'Parks', 'Dawson', 'Santiago', 'Norris', 'Hardy', 'Love', 'Steele', 'Curry', 'Powers', 'Schultz', 'Barker', 'Guzman', 'Page', 'Munoz', 'Ball', 'Keller', 'Chandler', 'Weber', 'Leonard', 'Walsh', 'Lyons', 'Ramsey', 'Wolfe', 'Schneider', 'Mullins', 'Benson', 'Sharp', 'Bowen', 'Daniel', 'Barber', 'Cummings', 'Hines', 'Baldwin', 'Griffith', 'Valdez', 'Hubbard', 'Salazar', 'Reeves', 'Warner', 'Stevenson', 'Burgess', 'Santos', 'Tate', 'Cross', 'Garner', 'Mann', 'Mack', 'Moss', 'Thornton', 'Dennis', 'McGee', 'Farmer', 'Delgado', 'Aguilar', 'Vega', 'Glover', 'Manning', 'Cohen', 'Harmon', 'Rodgers', 'Robbins', 'Newton', 'Todd', 'Blair', 'Higgins', 'Ingram', 'Reese', 'Cannon', 'Strickland', 'Townsend', 'Potter', 'Goodwin', 'Walton', 'Rowe', 'Hampton', 'Ortega', 'Patton', 'Swanson', 'Joseph', 'Francis', 'Goodman', 'Maldonado', 'Yates', 'Becker', 'Erickson', 'Hodges', 'Rios', 'Conner', 'Adkins', 'Webster', 'Norman', 'Malone', 'Hammond', 'Flowers', 'Cobb', 'Moody', 'Quinn', 'Blake', 'Maxwell', 'Pope', 'Floyd', 'Osborne', 'Paul', 'McCarthy', 'Guerrero', 'Lindsey', 'Estrada', 'Sandoval', 'Gibbs', 'Tyler', 'Gross', 'Fitzgerald', 'Stokes', 'Doyle', 'Sherman', 'Saunders', 'Wise', 'Colon', 'Gill', 'Alvarado', 'Greer', 'Padilla', 'Simon', 'Waters', 'Nunez', 'Ballard', 'Schwartz', 'McBride', 'Houston', 'Christensen', 'Klein', 'Pratt', 'Briggs', 'Parsons', 'McLaughlin', 'Zimmerman', 'French', 'Buchanan', 'Moran', 'Copeland', 'Roy', 'Pittman', 'Brady', 'McCormick', 'Holloway', 'Brock', 'Poole', 'Frank', 'Logan', 'Owen', 'Bass', 'Marsh', 'Drake', 'Wong', 'Jefferson', 'Park', 'Morton', 'Abbott', 'Sparks', 'Patrick', 'Norton', 'Huff', 'Clayton', 'Massey', 'Lloyd', 'Figueroa', 'Carson', 'Bowers', 'Roberson', 'Barton', 'Tran', 'Lamb', 'Harrington', 'Casey', 'Boone', 'Cortez', 'Clarke', 'Mathis', 'Singleton', 'Wilkins', 'Cain', 'Bryan', 'Underwood', 'Hogan', 'McKenzie', 'Collier', 'Luna', 'Phelps', 'McGuire', 'Allison', 'Bridges', 'Wilkerson', 'Nash', 'Summers', 'Atkins'], - // Data taken from http://www.dati.gov.it/dataset/comune-di-firenze_0164 (first 1000) - "it": ["Acciai", "Aglietti", "Agostini", "Agresti", "Ahmed", "Aiazzi", "Albanese", "Alberti", "Alessi", "Alfani", "Alinari", "Alterini", "Amato", "Ammannati", "Ancillotti", "Andrei", "Andreini", "Andreoni", "Angeli", "Anichini", "Antonelli", "Antonini", "Arena", "Ariani", "Arnetoli", "Arrighi", "Baccani", "Baccetti", "Bacci", "Bacherini", "Badii", "Baggiani", "Baglioni", "Bagni", "Bagnoli", "Baldassini", "Baldi", "Baldini", "Ballerini", "Balli", "Ballini", "Balloni", "Bambi", "Banchi", "Bandinelli", "Bandini", "Bani", "Barbetti", "Barbieri", "Barchielli", "Bardazzi", "Bardelli", "Bardi", "Barducci", "Bargellini", "Bargiacchi", "Barni", "Baroncelli", "Baroncini", "Barone", "Baroni", "Baronti", "Bartalesi", "Bartoletti", "Bartoli", "Bartolini", "Bartoloni", "Bartolozzi", "Basagni", "Basile", "Bassi", "Batacchi", "Battaglia", "Battaglini", "Bausi", "Becagli", "Becattini", "Becchi", "Becucci", "Bellandi", "Bellesi", "Belli", "Bellini", "Bellucci", "Bencini", "Benedetti", "Benelli", "Beni", "Benini", "Bensi", "Benucci", "Benvenuti", "Berlincioni", "Bernacchioni", "Bernardi", "Bernardini", "Berni", "Bernini", "Bertelli", "Berti", "Bertini", "Bessi", "Betti", "Bettini", "Biagi", "Biagini", "Biagioni", "Biagiotti", "Biancalani", "Bianchi", "Bianchini", "Bianco", "Biffoli", "Bigazzi", "Bigi", "Biliotti", "Billi", "Binazzi", "Bindi", "Bini", "Biondi", "Bizzarri", "Bocci", "Bogani", "Bolognesi", "Bonaiuti", "Bonanni", "Bonciani", "Boncinelli", "Bondi", "Bonechi", "Bongini", "Boni", "Bonini", "Borchi", "Boretti", "Borghi", "Borghini", "Borgioli", "Borri", "Borselli", "Boschi", "Bottai", "Bracci", "Braccini", "Brandi", "Braschi", "Bravi", "Brazzini", "Breschi", "Brilli", "Brizzi", "Brogelli", "Brogi", "Brogioni", "Brunelli", "Brunetti", "Bruni", "Bruno", "Brunori", "Bruschi", "Bucci", "Bucciarelli", "Buccioni", "Bucelli", "Bulli", "Burberi", "Burchi", "Burgassi", "Burroni", "Bussotti", "Buti", "Caciolli", "Caiani", "Calabrese", "Calamai", "Calamandrei", "Caldini", "Calo'", "Calonaci", "Calosi", "Calvelli", "Cambi", "Camiciottoli", "Cammelli", "Cammilli", "Campolmi", "Cantini", "Capanni", "Capecchi", "Caponi", "Cappelletti", "Cappelli", "Cappellini", "Cappugi", "Capretti", "Caputo", "Carbone", "Carboni", "Cardini", "Carlesi", "Carletti", "Carli", "Caroti", "Carotti", "Carrai", "Carraresi", "Carta", "Caruso", "Casalini", "Casati", "Caselli", "Casini", "Castagnoli", "Castellani", "Castelli", "Castellucci", "Catalano", "Catarzi", "Catelani", "Cavaciocchi", "Cavallaro", "Cavallini", "Cavicchi", "Cavini", "Ceccarelli", "Ceccatelli", "Ceccherelli", "Ceccherini", "Cecchi", "Cecchini", "Cecconi", "Cei", "Cellai", "Celli", "Cellini", "Cencetti", "Ceni", "Cenni", "Cerbai", "Cesari", "Ceseri", "Checcacci", "Checchi", "Checcucci", "Cheli", "Chellini", "Chen", "Cheng", "Cherici", "Cherubini", "Chiaramonti", "Chiarantini", "Chiarelli", "Chiari", "Chiarini", "Chiarugi", "Chiavacci", "Chiesi", "Chimenti", "Chini", "Chirici", "Chiti", "Ciabatti", "Ciampi", "Cianchi", "Cianfanelli", "Cianferoni", "Ciani", "Ciapetti", "Ciappi", "Ciardi", "Ciatti", "Cicali", "Ciccone", "Cinelli", "Cini", "Ciobanu", "Ciolli", "Cioni", "Cipriani", "Cirillo", "Cirri", "Ciucchi", "Ciuffi", "Ciulli", "Ciullini", "Clemente", "Cocchi", "Cognome", "Coli", "Collini", "Colombo", "Colzi", "Comparini", "Conforti", "Consigli", "Conte", "Conti", "Contini", "Coppini", "Coppola", "Corsi", "Corsini", "Corti", "Cortini", "Cosi", "Costa", "Costantini", "Costantino", "Cozzi", "Cresci", "Crescioli", "Cresti", "Crini", "Curradi", "D'Agostino", "D'Alessandro", "D'Amico", "D'Angelo", "Daddi", "Dainelli", "Dallai", "Danti", "Davitti", "De Angelis", "De Luca", "De Marco", "De Rosa", "De Santis", "De Simone", "De Vita", "Degl'Innocenti", "Degli Innocenti", "Dei", "Del Lungo", "Del Re", "Di Marco", "Di Stefano", "Dini", "Diop", "Dobre", "Dolfi", "Donati", "Dondoli", "Dong", "Donnini", "Ducci", "Dumitru", "Ermini", "Esposito", "Evangelisti", "Fabbri", "Fabbrini", "Fabbrizzi", "Fabbroni", "Fabbrucci", "Fabiani", "Facchini", "Faggi", "Fagioli", "Failli", "Faini", "Falciani", "Falcini", "Falcone", "Fallani", "Falorni", "Falsini", "Falugiani", "Fancelli", "Fanelli", "Fanetti", "Fanfani", "Fani", "Fantappie'", "Fantechi", "Fanti", "Fantini", "Fantoni", "Farina", "Fattori", "Favilli", "Fedi", "Fei", "Ferrante", "Ferrara", "Ferrari", "Ferraro", "Ferretti", "Ferri", "Ferrini", "Ferroni", "Fiaschi", "Fibbi", "Fiesoli", "Filippi", "Filippini", "Fini", "Fioravanti", "Fiore", "Fiorentini", "Fiorini", "Fissi", "Focardi", "Foggi", "Fontana", "Fontanelli", "Fontani", "Forconi", "Formigli", "Forte", "Forti", "Fortini", "Fossati", "Fossi", "Francalanci", "Franceschi", "Franceschini", "Franchi", "Franchini", "Franci", "Francini", "Francioni", "Franco", "Frassineti", "Frati", "Fratini", "Frilli", "Frizzi", "Frosali", "Frosini", "Frullini", "Fusco", "Fusi", "Gabbrielli", "Gabellini", "Gagliardi", "Galanti", "Galardi", "Galeotti", "Galletti", "Galli", "Gallo", "Gallori", "Gambacciani", "Gargani", "Garofalo", "Garuglieri", "Gashi", "Gasperini", "Gatti", "Gelli", "Gensini", "Gentile", "Gentili", "Geri", "Gerini", "Gheri", "Ghini", "Giachetti", "Giachi", "Giacomelli", "Gianassi", "Giani", "Giannelli", "Giannetti", "Gianni", "Giannini", "Giannoni", "Giannotti", "Giannozzi", "Gigli", "Giordano", "Giorgetti", "Giorgi", "Giovacchini", "Giovannelli", "Giovannetti", "Giovannini", "Giovannoni", "Giuliani", "Giunti", "Giuntini", "Giusti", "Gonnelli", "Goretti", "Gori", "Gradi", "Gramigni", "Grassi", "Grasso", "Graziani", "Grazzini", "Greco", "Grifoni", "Grillo", "Grimaldi", "Grossi", "Gualtieri", "Guarducci", "Guarino", "Guarnieri", "Guasti", "Guerra", "Guerri", "Guerrini", "Guidi", "Guidotti", "He", "Hoxha", "Hu", "Huang", "Iandelli", "Ignesti", "Innocenti", "Jin", "La Rosa", "Lai", "Landi", "Landini", "Lanini", "Lapi", "Lapini", "Lari", "Lascialfari", "Lastrucci", "Latini", "Lazzeri", "Lazzerini", "Lelli", "Lenzi", "Leonardi", "Leoncini", "Leone", "Leoni", "Lepri", "Li", "Liao", "Lin", "Linari", "Lippi", "Lisi", "Livi", "Lombardi", "Lombardini", "Lombardo", "Longo", "Lopez", "Lorenzi", "Lorenzini", "Lorini", "Lotti", "Lu", "Lucchesi", "Lucherini", "Lunghi", "Lupi", "Madiai", "Maestrini", "Maffei", "Maggi", "Maggini", "Magherini", "Magini", "Magnani", "Magnelli", "Magni", "Magnolfi", "Magrini", "Malavolti", "Malevolti", "Manca", "Mancini", "Manetti", "Manfredi", "Mangani", "Mannelli", "Manni", "Mannini", "Mannucci", "Manuelli", "Manzini", "Marcelli", "Marchese", "Marchetti", "Marchi", "Marchiani", "Marchionni", "Marconi", "Marcucci", "Margheri", "Mari", "Mariani", "Marilli", "Marinai", "Marinari", "Marinelli", "Marini", "Marino", "Mariotti", "Marsili", "Martelli", "Martinelli", "Martini", "Martino", "Marzi", "Masi", "Masini", "Masoni", "Massai", "Materassi", "Mattei", "Matteini", "Matteucci", "Matteuzzi", "Mattioli", "Mattolini", "Matucci", "Mauro", "Mazzanti", "Mazzei", "Mazzetti", "Mazzi", "Mazzini", "Mazzocchi", "Mazzoli", "Mazzoni", "Mazzuoli", "Meacci", "Mecocci", "Meini", "Melani", "Mele", "Meli", "Mengoni", "Menichetti", "Meoni", "Merlini", "Messeri", "Messina", "Meucci", "Miccinesi", "Miceli", "Micheli", "Michelini", "Michelozzi", "Migliori", "Migliorini", "Milani", "Miniati", "Misuri", "Monaco", "Montagnani", "Montagni", "Montanari", "Montelatici", "Monti", "Montigiani", "Montini", "Morandi", "Morandini", "Morelli", "Moretti", "Morganti", "Mori", "Morini", "Moroni", "Morozzi", "Mugnai", "Mugnaini", "Mustafa", "Naldi", "Naldini", "Nannelli", "Nanni", "Nannini", "Nannucci", "Nardi", "Nardini", "Nardoni", "Natali", "Ndiaye", "Nencetti", "Nencini", "Nencioni", "Neri", "Nesi", "Nesti", "Niccolai", "Niccoli", "Niccolini", "Nigi", "Nistri", "Nocentini", "Noferini", "Novelli", "Nucci", "Nuti", "Nutini", "Oliva", "Olivieri", "Olmi", "Orlandi", "Orlandini", "Orlando", "Orsini", "Ortolani", "Ottanelli", "Pacciani", "Pace", "Paci", "Pacini", "Pagani", "Pagano", "Paggetti", "Pagliai", "Pagni", "Pagnini", "Paladini", "Palagi", "Palchetti", "Palloni", "Palmieri", "Palumbo", "Pampaloni", "Pancani", "Pandolfi", "Pandolfini", "Panerai", "Panichi", "Paoletti", "Paoli", "Paolini", "Papi", "Papini", "Papucci", "Parenti", "Parigi", "Parisi", "Parri", "Parrini", "Pasquini", "Passeri", "Pecchioli", "Pecorini", "Pellegrini", "Pepi", "Perini", "Perrone", "Peruzzi", "Pesci", "Pestelli", "Petri", "Petrini", "Petrucci", "Pettini", "Pezzati", "Pezzatini", "Piani", "Piazza", "Piazzesi", "Piazzini", "Piccardi", "Picchi", "Piccini", "Piccioli", "Pieraccini", "Pieraccioni", "Pieralli", "Pierattini", "Pieri", "Pierini", "Pieroni", "Pietrini", "Pini", "Pinna", "Pinto", "Pinzani", "Pinzauti", "Piras", "Pisani", "Pistolesi", "Poggesi", "Poggi", "Poggiali", "Poggiolini", "Poli", "Pollastri", "Porciani", "Pozzi", "Pratellesi", "Pratesi", "Prosperi", "Pruneti", "Pucci", "Puccini", "Puccioni", "Pugi", "Pugliese", "Puliti", "Querci", "Quercioli", "Raddi", "Radu", "Raffaelli", "Ragazzini", "Ranfagni", "Ranieri", "Rastrelli", "Raugei", "Raveggi", "Renai", "Renzi", "Rettori", "Ricci", "Ricciardi", "Ridi", "Ridolfi", "Rigacci", "Righi", "Righini", "Rinaldi", "Risaliti", "Ristori", "Rizzo", "Rocchi", "Rocchini", "Rogai", "Romagnoli", "Romanelli", "Romani", "Romano", "Romei", "Romeo", "Romiti", "Romoli", "Romolini", "Rontini", "Rosati", "Roselli", "Rosi", "Rossetti", "Rossi", "Rossini", "Rovai", "Ruggeri", "Ruggiero", "Russo", "Sabatini", "Saccardi", "Sacchetti", "Sacchi", "Sacco", "Salerno", "Salimbeni", "Salucci", "Salvadori", "Salvestrini", "Salvi", "Salvini", "Sanesi", "Sani", "Sanna", "Santi", "Santini", "Santoni", "Santoro", "Santucci", "Sardi", "Sarri", "Sarti", "Sassi", "Sbolci", "Scali", "Scarpelli", "Scarselli", "Scopetani", "Secci", "Selvi", "Senatori", "Senesi", "Serafini", "Sereni", "Serra", "Sestini", "Sguanci", "Sieni", "Signorini", "Silvestri", "Simoncini", "Simonetti", "Simoni", "Singh", "Sodi", "Soldi", "Somigli", "Sorbi", "Sorelli", "Sorrentino", "Sottili", "Spina", "Spinelli", "Staccioli", "Staderini", "Stefanelli", "Stefani", "Stefanini", "Stella", "Susini", "Tacchi", "Tacconi", "Taddei", "Tagliaferri", "Tamburini", "Tanganelli", "Tani", "Tanini", "Tapinassi", "Tarchi", "Tarchiani", "Targioni", "Tassi", "Tassini", "Tempesti", "Terzani", "Tesi", "Testa", "Testi", "Tilli", "Tinti", "Tirinnanzi", "Toccafondi", "Tofanari", "Tofani", "Tognaccini", "Tonelli", "Tonini", "Torelli", "Torrini", "Tosi", "Toti", "Tozzi", "Trambusti", "Trapani", "Tucci", "Turchi", "Ugolini", "Ulivi", "Valente", "Valenti", "Valentini", "Vangelisti", "Vanni", "Vannini", "Vannoni", "Vannozzi", "Vannucchi", "Vannucci", "Ventura", "Venturi", "Venturini", "Vestri", "Vettori", "Vichi", "Viciani", "Vieri", "Vigiani", "Vignoli", "Vignolini", "Vignozzi", "Villani", "Vinci", "Visani", "Vitale", "Vitali", "Viti", "Viviani", "Vivoli", "Volpe", "Volpi", "Wang", "Wu", "Xu", "Yang", "Ye", "Zagli", "Zani", "Zanieri", "Zanobini", "Zecchi", "Zetti", "Zhang", "Zheng", "Zhou", "Zhu", "Zingoni", "Zini", "Zoppi"], - // http://www.voornamelijk.nl/meest-voorkomende-achternamen-in-nederland-en-amsterdam/ - "nl":["Albers", "Alblas", "Appelman", "Baars", "Baas", "Bakker", "Blank", "Bleeker", "Blok", "Blom", "Boer", "Boers", "Boldewijn", "Boon", "Boot", "Bos", "Bosch", "Bosma", "Bosman", "Bouma", "Bouman", "Bouwman", "Brands", "Brouwer", "Burger", "Buijs", "Buitenhuis", "Ceder", "Cohen", "Dekker", "Dekkers", "Dijkman", "Dijkstra", "Driessen", "Drost", "Engel", "Evers", "Faber", "Franke", "Gerritsen", "Goedhart", "Goossens", "Groen", "Groenenberg", "Groot", "Haan", "Hart", "Heemskerk", "Hendriks", "Hermans", "Hoekstra", "Hofman", "Hopman", "Huisman", "Jacobs", "Jansen", "Janssen", "Jonker", "Jaspers", "Keijzer", "Klaassen", "Klein", "Koek", "Koenders", "Kok", "Kool", "Koopman", "Koopmans", "Koning", "Koster", "Kramer", "Kroon", "Kuijpers", "Kuiper", "Kuipers", "Kurt", "Koster", "Kwakman", "Los", "Lubbers", "Maas", "Markus", "Martens", "Meijer", "Mol", "Molenaar", "Mulder", "Nieuwenhuis", "Peeters", "Peters", "Pengel", "Pieters", "Pool", "Post", "Postma", "Prins", "Pronk", "Reijnders", "Rietveld", "Roest", "Roos", "Sanders", "Schaap", "Scheffer", "Schenk", "Schilder", "Schipper", "Schmidt", "Scholten", "Schouten", "Schut", "Schutte", "Schuurman", "Simons", "Smeets", "Smit", "Smits", "Snel", "Swinkels", "Tas", "Terpstra", "Timmermans", "Tol", "Tromp", "Troost", "Valk", "Veenstra", "Veldkamp", "Verbeek", "Verheul", "Verhoeven", "Vermeer", "Vermeulen", "Verweij", "Vink", "Visser", "Voorn", "Vos", "Wagenaar", "Wiersema", "Willems", "Willemsen", "Witteveen", "Wolff", "Wolters", "Zijlstra", "Zwart", "de Beer", "de Boer", "de Bruijn", "de Bruin", "de Graaf", "de Groot", "de Haan", "de Haas", "de Jager", "de Jong", "de Jonge", "de Koning", "de Lange", "de Leeuw", "de Ridder", "de Rooij", "de Ruiter", "de Vos", "de Vries", "de Waal", "de Wit", "de Zwart", "van Beek", "van Boven", "van Dam", "van Dijk", "van Dongen", "van Doorn", "van Egmond", "van Eijk", "van Es", "van Gelder", "van Gelderen", "van Houten", "van Hulst", "van Kempen", "van Kesteren", "van Leeuwen", "van Loon", "van Mill", "van Noord", "van Ommen", "van Ommeren", "van Oosten", "van Oostveen", "van Rijn", "van Schaik", "van Veen", "van Vliet", "van Wijk", "van Wijngaarden", "van den Poel", "van de Pol", "van den Ploeg", "van de Ven", "van den Berg", "van den Bosch", "van den Brink", "van den Broek", "van den Heuvel", "van der Heijden", "van der Horst", "van der Hulst", "van der Kroon", "van der Laan", "van der Linden", "van der Meer", "van der Meij", "van der Meulen", "van der Molen", "van der Sluis", "van der Spek", "van der Veen", "van der Velde", "van der Velden", "van der Vliet", "van der Wal"], - // https://surnames.behindthename.com/top/lists/england-wales/1991 - "uk":["Smith","Jones","Williams","Taylor","Brown","Davies","Evans","Wilson","Thomas","Johnson","Roberts","Robinson","Thompson","Wright","Walker","White","Edwards","Hughes","Green","Hall","Lewis","Harris","Clarke","Patel","Jackson","Wood","Turner","Martin","Cooper","Hill","Ward","Morris","Moore","Clark","Lee","King","Baker","Harrison","Morgan","Allen","James","Scott","Phillips","Watson","Davis","Parker","Price","Bennett","Young","Griffiths","Mitchell","Kelly","Cook","Carter","Richardson","Bailey","Collins","Bell","Shaw","Murphy","Miller","Cox","Richards","Khan","Marshall","Anderson","Simpson","Ellis","Adams","Singh","Begum","Wilkinson","Foster","Chapman","Powell","Webb","Rogers","Gray","Mason","Ali","Hunt","Hussain","Campbell","Matthews","Owen","Palmer","Holmes","Mills","Barnes","Knight","Lloyd","Butler","Russell","Barker","Fisher","Stevens","Jenkins","Murray","Dixon","Harvey","Graham","Pearson","Ahmed","Fletcher","Walsh","Kaur","Gibson","Howard","Andrews","Stewart","Elliott","Reynolds","Saunders","Payne","Fox","Ford","Pearce","Day","Brooks","West","Lawrence","Cole","Atkinson","Bradley","Spencer","Gill","Dawson","Ball","Burton","O'brien","Watts","Rose","Booth","Perry","Ryan","Grant","Wells","Armstrong","Francis","Rees","Hayes","Hart","Hudson","Newman","Barrett","Webster","Hunter","Gregory","Carr","Lowe","Page","Marsh","Riley","Dunn","Woods","Parsons","Berry","Stone","Reid","Holland","Hawkins","Harding","Porter","Robertson","Newton","Oliver","Reed","Kennedy","Williamson","Bird","Gardner","Shah","Dean","Lane","Cooke","Bates","Henderson","Parry","Burgess","Bishop","Walton","Burns","Nicholson","Shepherd","Ross","Cross","Long","Freeman","Warren","Nicholls","Hamilton","Byrne","Sutton","Mcdonald","Yates","Hodgson","Robson","Curtis","Hopkins","O'connor","Harper","Coleman","Watkins","Moss","Mccarthy","Chambers","O'neill","Griffin","Sharp","Hardy","Wheeler","Potter","Osborne","Johnston","Gordon","Doyle","Wallace","George","Jordan","Hutchinson","Rowe","Burke","May","Pritchard","Gilbert","Willis","Higgins","Read","Miles","Stevenson","Stephenson","Hammond","Arnold","Buckley","Walters","Hewitt","Barber","Nelson","Slater","Austin","Sullivan","Whitehead","Mann","Frost","Lambert","Stephens","Blake","Akhtar","Lynch","Goodwin","Barton","Woodward","Thomson","Cunningham","Quinn","Barnett","Baxter","Bibi","Clayton","Nash","Greenwood","Jennings","Holt","Kemp","Poole","Gallagher","Bond","Stokes","Tucker","Davidson","Fowler","Heath","Norman","Middleton","Lawson","Banks","French","Stanley","Jarvis","Gibbs","Ferguson","Hayward","Carroll","Douglas","Dickinson","Todd","Barlow","Peters","Lucas","Knowles","Hartley","Miah","Simmons","Morton","Alexander","Field","Morrison","Norris","Townsend","Preston","Hancock","Thornton","Baldwin","Burrows","Briggs","Parkinson","Reeves","Macdonald","Lamb","Black","Abbott","Sanders","Thorpe","Holden","Tomlinson","Perkins","Ashton","Rhodes","Fuller","Howe","Bryant","Vaughan","Dale","Davey","Weston","Bartlett","Whittaker","Davison","Kent","Skinner","Birch","Morley","Daniels","Glover","Howell","Cartwright","Pugh","Humphreys","Goddard","Brennan","Wall","Kirby","Bowen","Savage","Bull","Wong","Dobson","Smart","Wilkins","Kirk","Fraser","Duffy","Hicks","Patterson","Bradshaw","Little","Archer","Warner","Waters","O'sullivan","Farrell","Brookes","Atkins","Kay","Dodd","Bentley","Flynn","John","Schofield","Short","Haynes","Wade","Butcher","Henry","Sanderson","Crawford","Sheppard","Bolton","Coates","Giles","Gould","Houghton","Gibbons","Pratt","Manning","Law","Hooper","Noble","Dyer","Rahman","Clements","Moran","Sykes","Chan","Doherty","Connolly","Joyce","Franklin","Hobbs","Coles","Herbert","Steele","Kerr","Leach","Winter","Owens","Duncan","Naylor","Fleming","Horton","Finch","Fitzgerald","Randall","Carpenter","Marsden","Browne","Garner","Pickering","Hale","Dennis","Vincent","Chadwick","Chandler","Sharpe","Nolan","Lyons","Hurst","Collier","Peacock","Howarth","Faulkner","Rice","Pollard","Welch","Norton","Gough","Sinclair","Blackburn","Bryan","Conway","Power","Cameron","Daly","Allan","Hanson","Gardiner","Boyle","Myers","Turnbull","Wallis","Mahmood","Sims","Swift","Iqbal","Pope","Brady","Chamberlain","Rowley","Tyler","Farmer","Metcalfe","Hilton","Godfrey","Holloway","Parkin","Bray","Talbot","Donnelly","Nixon","Charlton","Benson","Whitehouse","Barry","Hope","Lord","North","Storey","Connor","Potts","Bevan","Hargreaves","Mclean","Mistry","Bruce","Howells","Hyde","Parkes","Wyatt","Fry","Lees","O'donnell","Craig","Forster","Mckenzie","Humphries","Mellor","Carey","Ingram","Summers","Leonard"], - // https://surnames.behindthename.com/top/lists/germany/2017 - "de": ["Müller","Schmidt","Schneider","Fischer","Weber","Meyer","Wagner","Becker","Schulz","Hoffmann","Schäfer","Koch","Bauer","Richter","Klein","Wolf","Schröder","Neumann","Schwarz","Zimmermann","Braun","Krüger","Hofmann","Hartmann","Lange","Schmitt","Werner","Schmitz","Krause","Meier","Lehmann","Schmid","Schulze","Maier","Köhler","Herrmann","König","Walter","Mayer","Huber","Kaiser","Fuchs","Peters","Lang","Scholz","Möller","Weiß","Jung","Hahn","Schubert","Vogel","Friedrich","Keller","Günther","Frank","Berger","Winkler","Roth","Beck","Lorenz","Baumann","Franke","Albrecht","Schuster","Simon","Ludwig","Böhm","Winter","Kraus","Martin","Schumacher","Krämer","Vogt","Stein","Jäger","Otto","Sommer","Groß","Seidel","Heinrich","Brandt","Haas","Schreiber","Graf","Schulte","Dietrich","Ziegler","Kuhn","Kühn","Pohl","Engel","Horn","Busch","Bergmann","Thomas","Voigt","Sauer","Arnold","Wolff","Pfeiffer"], - // http://www.japantimes.co.jp/life/2009/10/11/lifestyle/japans-top-100-most-common-family-names/ - "jp": ["Sato","Suzuki","Takahashi","Tanaka","Watanabe","Ito","Yamamoto","Nakamura","Kobayashi","Kato","Yoshida","Yamada","Sasaki","Yamaguchi","Saito","Matsumoto","Inoue","Kimura","Hayashi","Shimizu","Yamazaki","Mori","Abe","Ikeda","Hashimoto","Yamashita","Ishikawa","Nakajima","Maeda","Fujita","Ogawa","Goto","Okada","Hasegawa","Murakami","Kondo","Ishii","Saito","Sakamoto","Endo","Aoki","Fujii","Nishimura","Fukuda","Ota","Miura","Fujiwara","Okamoto","Matsuda","Nakagawa","Nakano","Harada","Ono","Tamura","Takeuchi","Kaneko","Wada","Nakayama","Ishida","Ueda","Morita","Hara","Shibata","Sakai","Kudo","Yokoyama","Miyazaki","Miyamoto","Uchida","Takagi","Ando","Taniguchi","Ohno","Maruyama","Imai","Takada","Fujimoto","Takeda","Murata","Ueno","Sugiyama","Masuda","Sugawara","Hirano","Kojima","Otsuka","Chiba","Kubo","Matsui","Iwasaki","Sakurai","Kinoshita","Noguchi","Matsuo","Nomura","Kikuchi","Sano","Onishi","Sugimoto","Arai"], - // http://www.lowchensaustralia.com/names/popular-spanish-names.htm - "es": ["Garcia","Fernandez","Lopez","Martinez","Gonzalez","Rodriguez","Sanchez","Perez","Martin","Gomez","Ruiz","Diaz","Hernandez","Alvarez","Jimenez","Moreno","Munoz","Alonso","Romero","Navarro","Gutierrez","Torres","Dominguez","Gil","Vazquez","Blanco","Serrano","Ramos","Castro","Suarez","Sanz","Rubio","Ortega","Molina","Delgado","Ortiz","Morales","Ramirez","Marin","Iglesias","Santos","Castillo","Garrido","Calvo","Pena","Cruz","Cano","Nunez","Prieto","Diez","Lozano","Vidal","Pascual","Ferrer","Medina","Vega","Leon","Herrero","Vicente","Mendez","Guerrero","Fuentes","Campos","Nieto","Cortes","Caballero","Ibanez","Lorenzo","Pastor","Gimenez","Saez","Soler","Marquez","Carrasco","Herrera","Montero","Arias","Crespo","Flores","Andres","Aguilar","Hidalgo","Cabrera","Mora","Duran","Velasco","Rey","Pardo","Roman","Vila","Bravo","Merino","Moya","Soto","Izquierdo","Reyes","Redondo","Marcos","Carmona","Menendez"], - // Data taken from https://fr.wikipedia.org/wiki/Liste_des_noms_de_famille_les_plus_courants_en_France - "fr": ["Martin","Bernard","Thomas","Petit","Robert","Richard","Durand","Dubois","Moreau","Laurent","Simon","Michel","Lefèvre","Leroy","Roux","David","Bertrand","Morel","Fournier","Girard","Bonnet","Dupont","Lambert","Fontaine","Rousseau","Vincent","Müller","Lefèvre","Faure","André","Mercier","Blanc","Guérin","Boyer","Garnier","Chevalier","François","Legrand","Gauthier","Garcia","Perrin","Robin","Clément","Morin","Nicolas","Henry","Roussel","Matthieu","Gautier","Masson","Marchand","Duval","Denis","Dumont","Marie","Lemaire","Noël","Meyer","Dufour","Meunier","Brun","Blanchard","Giraud","Joly","Rivière","Lucas","Brunet","Gaillard","Barbier","Arnaud","Martínez","Gérard","Roche","Renard","Schmitt","Roy","Leroux","Colin","Vidal","Caron","Picard","Roger","Fabre","Aubert","Lemoine","Renaud","Dumas","Lacroix","Olivier","Philippe","Bourgeois","Pierre","Benoît","Rey","Leclerc","Payet","Rolland","Leclercq","Guillaume","Lecomte","López","Jean","Dupuy","Guillot","Hubert","Berger","Carpentier","Sánchez","Dupuis","Moulin","Louis","Deschamps","Huet","Vasseur","Perez","Boucher","Fleury","Royer","Klein","Jacquet","Adam","Paris","Poirier","Marty","Aubry","Guyot","Carré","Charles","Renault","Charpentier","Ménard","Maillard","Baron","Bertin","Bailly","Hervé","Schneider","Fernández","Le GallGall","Collet","Léger","Bouvier","Julien","Prévost","Millet","Perrot","Daniel","Le RouxRoux","Cousin","Germain","Breton","Besson","Langlois","Rémi","Le GoffGoff","Pelletier","Lévêque","Perrier","Leblanc","Barré","Lebrun","Marchal","Weber","Mallet","Hamon","Boulanger","Jacob","Monnier","Michaud","Rodríguez","Guichard","Gillet","Étienne","Grondin","Poulain","Tessier","Chevallier","Collin","Chauvin","Da SilvaSilva","Bouchet","Gay","Lemaître","Bénard","Maréchal","Humbert","Reynaud","Antoine","Hoarau","Perret","Barthélemy","Cordier","Pichon","Lejeune","Gilbert","Lamy","Delaunay","Pasquier","Carlier","LaporteLaporte"] - }, - - // Data taken from http://geoportal.statistics.gov.uk/datasets/ons-postcode-directory-latest-centroids - postcodeAreas: [{code: 'AB'}, {code: 'AL'}, {code: 'B'}, {code: 'BA'}, {code: 'BB'}, {code: 'BD'}, {code: 'BH'}, {code: 'BL'}, {code: 'BN'}, {code: 'BR'}, {code: 'BS'}, {code: 'BT'}, {code: 'CA'}, {code: 'CB'}, {code: 'CF'}, {code: 'CH'}, {code: 'CM'}, {code: 'CO'}, {code: 'CR'}, {code: 'CT'}, {code: 'CV'}, {code: 'CW'}, {code: 'DA'}, {code: 'DD'}, {code: 'DE'}, {code: 'DG'}, {code: 'DH'}, {code: 'DL'}, {code: 'DN'}, {code: 'DT'}, {code: 'DY'}, {code: 'E'}, {code: 'EC'}, {code: 'EH'}, {code: 'EN'}, {code: 'EX'}, {code: 'FK'}, {code: 'FY'}, {code: 'G'}, {code: 'GL'}, {code: 'GU'}, {code: 'GY'}, {code: 'HA'}, {code: 'HD'}, {code: 'HG'}, {code: 'HP'}, {code: 'HR'}, {code: 'HS'}, {code: 'HU'}, {code: 'HX'}, {code: 'IG'}, {code: 'IM'}, {code: 'IP'}, {code: 'IV'}, {code: 'JE'}, {code: 'KA'}, {code: 'KT'}, {code: 'KW'}, {code: 'KY'}, {code: 'L'}, {code: 'LA'}, {code: 'LD'}, {code: 'LE'}, {code: 'LL'}, {code: 'LN'}, {code: 'LS'}, {code: 'LU'}, {code: 'M'}, {code: 'ME'}, {code: 'MK'}, {code: 'ML'}, {code: 'N'}, {code: 'NE'}, {code: 'NG'}, {code: 'NN'}, {code: 'NP'}, {code: 'NR'}, {code: 'NW'}, {code: 'OL'}, {code: 'OX'}, {code: 'PA'}, {code: 'PE'}, {code: 'PH'}, {code: 'PL'}, {code: 'PO'}, {code: 'PR'}, {code: 'RG'}, {code: 'RH'}, {code: 'RM'}, {code: 'S'}, {code: 'SA'}, {code: 'SE'}, {code: 'SG'}, {code: 'SK'}, {code: 'SL'}, {code: 'SM'}, {code: 'SN'}, {code: 'SO'}, {code: 'SP'}, {code: 'SR'}, {code: 'SS'}, {code: 'ST'}, {code: 'SW'}, {code: 'SY'}, {code: 'TA'}, {code: 'TD'}, {code: 'TF'}, {code: 'TN'}, {code: 'TQ'}, {code: 'TR'}, {code: 'TS'}, {code: 'TW'}, {code: 'UB'}, {code: 'W'}, {code: 'WA'}, {code: 'WC'}, {code: 'WD'}, {code: 'WF'}, {code: 'WN'}, {code: 'WR'}, {code: 'WS'}, {code: 'WV'}, {code: 'YO'}, {code: 'ZE'}], - - // Data taken from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 - countries: [{"name":"Afghanistan","abbreviation":"AF"},{"name":"Åland Islands","abbreviation":"AX"},{"name":"Albania","abbreviation":"AL"},{"name":"Algeria","abbreviation":"DZ"},{"name":"American Samoa","abbreviation":"AS"},{"name":"Andorra","abbreviation":"AD"},{"name":"Angola","abbreviation":"AO"},{"name":"Anguilla","abbreviation":"AI"},{"name":"Antarctica","abbreviation":"AQ"},{"name":"Antigua and Barbuda","abbreviation":"AG"},{"name":"Argentina","abbreviation":"AR"},{"name":"Armenia","abbreviation":"AM"},{"name":"Aruba","abbreviation":"AW"},{"name":"Australia","abbreviation":"AU"},{"name":"Austria","abbreviation":"AT"},{"name":"Azerbaijan","abbreviation":"AZ"},{"name":"Bahamas","abbreviation":"BS"},{"name":"Bahrain","abbreviation":"BH"},{"name":"Bangladesh","abbreviation":"BD"},{"name":"Barbados","abbreviation":"BB"},{"name":"Belarus","abbreviation":"BY"},{"name":"Belgium","abbreviation":"BE"},{"name":"Belize","abbreviation":"BZ"},{"name":"Benin","abbreviation":"BJ"},{"name":"Bermuda","abbreviation":"BM"},{"name":"Bhutan","abbreviation":"BT"},{"name":"Plurinational State of Bolivia","abbreviation":"BO"},{"name":"Bonaire, Sint Eustatius and Saba","abbreviation":"BQ"},{"name":"Bosnia and Herzegovina","abbreviation":"BA"},{"name":"Botswana","abbreviation":"BW"},{"name":"Bouvet Island","abbreviation":"BV"},{"name":"Brazil","abbreviation":"BR"},{"name":"British Indian Ocean Territory","abbreviation":"IO"},{"name":"Brunei Darussalam","abbreviation":"BN"},{"name":"Bulgaria","abbreviation":"BG"},{"name":"Burkina Faso","abbreviation":"BF"},{"name":"Burundi","abbreviation":"BI"},{"name":"Cabo Verde","abbreviation":"CV"},{"name":"Cambodia","abbreviation":"KH"},{"name":"Cameroon","abbreviation":"CM"},{"name":"Canada","abbreviation":"CA"},{"name":"Cayman Islands","abbreviation":"KY"},{"name":"Central African Republic","abbreviation":"CF"},{"name":"Chad","abbreviation":"TD"},{"name":"Chile","abbreviation":"CL"},{"name":"China","abbreviation":"CN"},{"name":"Christmas Island","abbreviation":"CX"},{"name":"Cocos (Keeling) Islands","abbreviation":"CC"},{"name":"Colombia","abbreviation":"CO"},{"name":"Comoros","abbreviation":"KM"},{"name":"Congo","abbreviation":"CG"},{"name":"Democratic Republic of the Congo","abbreviation":"CD"},{"name":"Cook Islands","abbreviation":"CK"},{"name":"Costa Rica","abbreviation":"CR"},{"name":"Côte d'Ivoire","abbreviation":"CI"},{"name":"Croatia","abbreviation":"HR"},{"name":"Cuba","abbreviation":"CU"},{"name":"Curaçao","abbreviation":"CW"},{"name":"Cyprus","abbreviation":"CY"},{"name":"Czechia","abbreviation":"CZ"},{"name":"Denmark","abbreviation":"DK"},{"name":"Djibouti","abbreviation":"DJ"},{"name":"Dominica","abbreviation":"DM"},{"name":"Dominican Republic","abbreviation":"DO"},{"name":"Ecuador","abbreviation":"EC"},{"name":"Egypt","abbreviation":"EG"},{"name":"El Salvador","abbreviation":"SV"},{"name":"Equatorial Guinea","abbreviation":"GQ"},{"name":"Eritrea","abbreviation":"ER"},{"name":"Estonia","abbreviation":"EE"},{"name":"Eswatini","abbreviation":"SZ"},{"name":"Ethiopia","abbreviation":"ET"},{"name":"Falkland Islands (Malvinas)","abbreviation":"FK"},{"name":"Faroe Islands","abbreviation":"FO"},{"name":"Fiji","abbreviation":"FJ"},{"name":"Finland","abbreviation":"FI"},{"name":"France","abbreviation":"FR"},{"name":"French Guiana","abbreviation":"GF"},{"name":"French Polynesia","abbreviation":"PF"},{"name":"French Southern Territories","abbreviation":"TF"},{"name":"Gabon","abbreviation":"GA"},{"name":"Gambia","abbreviation":"GM"},{"name":"Georgia","abbreviation":"GE"},{"name":"Germany","abbreviation":"DE"},{"name":"Ghana","abbreviation":"GH"},{"name":"Gibraltar","abbreviation":"GI"},{"name":"Greece","abbreviation":"GR"},{"name":"Greenland","abbreviation":"GL"},{"name":"Grenada","abbreviation":"GD"},{"name":"Guadeloupe","abbreviation":"GP"},{"name":"Guam","abbreviation":"GU"},{"name":"Guatemala","abbreviation":"GT"},{"name":"Guernsey","abbreviation":"GG"},{"name":"Guinea","abbreviation":"GN"},{"name":"Guinea-Bissau","abbreviation":"GW"},{"name":"Guyana","abbreviation":"GY"},{"name":"Haiti","abbreviation":"HT"},{"name":"Heard Island and McDonald Islands","abbreviation":"HM"},{"name":"Holy See","abbreviation":"VA"},{"name":"Honduras","abbreviation":"HN"},{"name":"Hong Kong","abbreviation":"HK"},{"name":"Hungary","abbreviation":"HU"},{"name":"Iceland","abbreviation":"IS"},{"name":"India","abbreviation":"IN"},{"name":"Indonesia","abbreviation":"ID"},{"name":"Islamic Republic of Iran","abbreviation":"IR"},{"name":"Iraq","abbreviation":"IQ"},{"name":"Ireland","abbreviation":"IE"},{"name":"Isle of Man","abbreviation":"IM"},{"name":"Israel","abbreviation":"IL"},{"name":"Italy","abbreviation":"IT"},{"name":"Jamaica","abbreviation":"JM"},{"name":"Japan","abbreviation":"JP"},{"name":"Jersey","abbreviation":"JE"},{"name":"Jordan","abbreviation":"JO"},{"name":"Kazakhstan","abbreviation":"KZ"},{"name":"Kenya","abbreviation":"KE"},{"name":"Kiribati","abbreviation":"KI"},{"name":"Democratic People's Republic of Korea","abbreviation":"KP"},{"name":"Republic of Korea","abbreviation":"KR"},{"name":"Kuwait","abbreviation":"KW"},{"name":"Kyrgyzstan","abbreviation":"KG"},{"name":"Lao People's Democratic Republic","abbreviation":"LA"},{"name":"Latvia","abbreviation":"LV"},{"name":"Lebanon","abbreviation":"LB"},{"name":"Lesotho","abbreviation":"LS"},{"name":"Liberia","abbreviation":"LR"},{"name":"Libya","abbreviation":"LY"},{"name":"Liechtenstein","abbreviation":"LI"},{"name":"Lithuania","abbreviation":"LT"},{"name":"Luxembourg","abbreviation":"LU"},{"name":"Macao","abbreviation":"MO"},{"name":"Madagascar","abbreviation":"MG"},{"name":"Malawi","abbreviation":"MW"},{"name":"Malaysia","abbreviation":"MY"},{"name":"Maldives","abbreviation":"MV"},{"name":"Mali","abbreviation":"ML"},{"name":"Malta","abbreviation":"MT"},{"name":"Marshall Islands","abbreviation":"MH"},{"name":"Martinique","abbreviation":"MQ"},{"name":"Mauritania","abbreviation":"MR"},{"name":"Mauritius","abbreviation":"MU"},{"name":"Mayotte","abbreviation":"YT"},{"name":"Mexico","abbreviation":"MX"},{"name":"Federated States of Micronesia","abbreviation":"FM"},{"name":"Republic of Moldova","abbreviation":"MD"},{"name":"Monaco","abbreviation":"MC"},{"name":"Mongolia","abbreviation":"MN"},{"name":"Montenegro","abbreviation":"ME"},{"name":"Montserrat","abbreviation":"MS"},{"name":"Morocco","abbreviation":"MA"},{"name":"Mozambique","abbreviation":"MZ"},{"name":"Myanmar","abbreviation":"MM"},{"name":"Namibia","abbreviation":"NA"},{"name":"Nauru","abbreviation":"NR"},{"name":"Nepal","abbreviation":"NP"},{"name":"Kingdom of the Netherlands","abbreviation":"NL"},{"name":"New Caledonia","abbreviation":"NC"},{"name":"New Zealand","abbreviation":"NZ"},{"name":"Nicaragua","abbreviation":"NI"},{"name":"Niger","abbreviation":"NE"},{"name":"Nigeria","abbreviation":"NG"},{"name":"Niue","abbreviation":"NU"},{"name":"Norfolk Island","abbreviation":"NF"},{"name":"North Macedonia","abbreviation":"MK"},{"name":"Northern Mariana Islands","abbreviation":"MP"},{"name":"Norway","abbreviation":"NO"},{"name":"Oman","abbreviation":"OM"},{"name":"Pakistan","abbreviation":"PK"},{"name":"Palau","abbreviation":"PW"},{"name":"State of Palestine","abbreviation":"PS"},{"name":"Panama","abbreviation":"PA"},{"name":"Papua New Guinea","abbreviation":"PG"},{"name":"Paraguay","abbreviation":"PY"},{"name":"Peru","abbreviation":"PE"},{"name":"Philippines","abbreviation":"PH"},{"name":"Pitcairn","abbreviation":"PN"},{"name":"Poland","abbreviation":"PL"},{"name":"Portugal","abbreviation":"PT"},{"name":"Puerto Rico","abbreviation":"PR"},{"name":"Qatar","abbreviation":"QA"},{"name":"Réunion","abbreviation":"RE"},{"name":"Romania","abbreviation":"RO"},{"name":"Russian Federation","abbreviation":"RU"},{"name":"Rwanda","abbreviation":"RW"},{"name":"Saint Barthélemy","abbreviation":"BL"},{"name":"Saint Helena, Ascension and Tristan da Cunha","abbreviation":"SH"},{"name":"Saint Kitts and Nevis","abbreviation":"KN"},{"name":"Saint Lucia","abbreviation":"LC"},{"name":"Saint Martin (French part)","abbreviation":"MF"},{"name":"Saint Pierre and Miquelon","abbreviation":"PM"},{"name":"Saint Vincent and the Grenadines","abbreviation":"VC"},{"name":"Samoa","abbreviation":"WS"},{"name":"San Marino","abbreviation":"SM"},{"name":"Sao Tome and Principe","abbreviation":"ST"},{"name":"Saudi Arabia","abbreviation":"SA"},{"name":"Senegal","abbreviation":"SN"},{"name":"Serbia","abbreviation":"RS"},{"name":"Seychelles","abbreviation":"SC"},{"name":"Sierra Leone","abbreviation":"SL"},{"name":"Singapore","abbreviation":"SG"},{"name":"Sint Maarten (Dutch part)","abbreviation":"SX"},{"name":"Slovakia","abbreviation":"SK"},{"name":"Slovenia","abbreviation":"SI"},{"name":"Solomon Islands","abbreviation":"SB"},{"name":"Somalia","abbreviation":"SO"},{"name":"South Africa","abbreviation":"ZA"},{"name":"South Georgia and the South Sandwich Islands","abbreviation":"GS"},{"name":"South Sudan","abbreviation":"SS"},{"name":"Spain","abbreviation":"ES"},{"name":"Sri Lanka","abbreviation":"LK"},{"name":"Sudan","abbreviation":"SD"},{"name":"Suriname","abbreviation":"SR"},{"name":"Svalbard and Jan Mayen","abbreviation":"SJ"},{"name":"Sweden","abbreviation":"SE"},{"name":"Switzerland","abbreviation":"CH"},{"name":"Syrian Arab Republic","abbreviation":"SY"},{"name":"Taiwan, Province of China","abbreviation":"TW"},{"name":"Tajikistan","abbreviation":"TJ"},{"name":"United Republic of Tanzania","abbreviation":"TZ"},{"name":"Thailand","abbreviation":"TH"},{"name":"Timor-Leste","abbreviation":"TL"},{"name":"Togo","abbreviation":"TG"},{"name":"Tokelau","abbreviation":"TK"},{"name":"Tonga","abbreviation":"TO"},{"name":"Trinidad and Tobago","abbreviation":"TT"},{"name":"Tunisia","abbreviation":"TN"},{"name":"Türkiye","abbreviation":"TR"},{"name":"Turkmenistan","abbreviation":"TM"},{"name":"Turks and Caicos Islands","abbreviation":"TC"},{"name":"Tuvalu","abbreviation":"TV"},{"name":"Uganda","abbreviation":"UG"},{"name":"Ukraine","abbreviation":"UA"},{"name":"United Arab Emirates","abbreviation":"AE"},{"name":"United Kingdom of Great Britain and Northern Ireland","abbreviation":"GB"},{"name":"United States Minor Outlying Islands","abbreviation":"UM"},{"name":"United States of America","abbreviation":"US"},{"name":"Uruguay","abbreviation":"UY"},{"name":"Uzbekistan","abbreviation":"UZ"},{"name":"Vanuatu","abbreviation":"VU"},{"name":"Bolivarian Republic of Venezuela","abbreviation":"VE"},{"name":"Viet Nam","abbreviation":"VN"},{"name":"Virgin Islands (British)","abbreviation":"VG"},{"name":"Virgin Islands (U.S.)","abbreviation":"VI"},{"name":"Wallis and Futuna","abbreviation":"WF"},{"name":"Western Sahara","abbreviation":"EH"},{"name":"Yemen","abbreviation":"YE"},{"name":"Zambia","abbreviation":"ZM"},{"name":"Zimbabwe","abbreviation":"ZW"}], - - counties: { - // Data taken from http://www.downloadexcelfiles.com/gb_en/download-excel-file-list-counties-uk - "uk": [ - {name: 'Bath and North East Somerset'}, - {name: 'Aberdeenshire'}, - {name: 'Anglesey'}, - {name: 'Angus'}, - {name: 'Bedford'}, - {name: 'Blackburn with Darwen'}, - {name: 'Blackpool'}, - {name: 'Bournemouth'}, - {name: 'Bracknell Forest'}, - {name: 'Brighton & Hove'}, - {name: 'Bristol'}, - {name: 'Buckinghamshire'}, - {name: 'Cambridgeshire'}, - {name: 'Carmarthenshire'}, - {name: 'Central Bedfordshire'}, - {name: 'Ceredigion'}, - {name: 'Cheshire East'}, - {name: 'Cheshire West and Chester'}, - {name: 'Clackmannanshire'}, - {name: 'Conwy'}, - {name: 'Cornwall'}, - {name: 'County Antrim'}, - {name: 'County Armagh'}, - {name: 'County Down'}, - {name: 'County Durham'}, - {name: 'County Fermanagh'}, - {name: 'County Londonderry'}, - {name: 'County Tyrone'}, - {name: 'Cumbria'}, - {name: 'Darlington'}, - {name: 'Denbighshire'}, - {name: 'Derby'}, - {name: 'Derbyshire'}, - {name: 'Devon'}, - {name: 'Dorset'}, - {name: 'Dumfries and Galloway'}, - {name: 'Dundee'}, - {name: 'East Lothian'}, - {name: 'East Riding of Yorkshire'}, - {name: 'East Sussex'}, - {name: 'Edinburgh?'}, - {name: 'Essex'}, - {name: 'Falkirk'}, - {name: 'Fife'}, - {name: 'Flintshire'}, - {name: 'Gloucestershire'}, - {name: 'Greater London'}, - {name: 'Greater Manchester'}, - {name: 'Gwent'}, - {name: 'Gwynedd'}, - {name: 'Halton'}, - {name: 'Hampshire'}, - {name: 'Hartlepool'}, - {name: 'Herefordshire'}, - {name: 'Hertfordshire'}, - {name: 'Highlands'}, - {name: 'Hull'}, - {name: 'Isle of Wight'}, - {name: 'Isles of Scilly'}, - {name: 'Kent'}, - {name: 'Lancashire'}, - {name: 'Leicester'}, - {name: 'Leicestershire'}, - {name: 'Lincolnshire'}, - {name: 'Lothian'}, - {name: 'Luton'}, - {name: 'Medway'}, - {name: 'Merseyside'}, - {name: 'Mid Glamorgan'}, - {name: 'Middlesbrough'}, - {name: 'Milton Keynes'}, - {name: 'Monmouthshire'}, - {name: 'Moray'}, - {name: 'Norfolk'}, - {name: 'North East Lincolnshire'}, - {name: 'North Lincolnshire'}, - {name: 'North Somerset'}, - {name: 'North Yorkshire'}, - {name: 'Northamptonshire'}, - {name: 'Northumberland'}, - {name: 'Nottingham'}, - {name: 'Nottinghamshire'}, - {name: 'Oxfordshire'}, - {name: 'Pembrokeshire'}, - {name: 'Perth and Kinross'}, - {name: 'Peterborough'}, - {name: 'Plymouth'}, - {name: 'Poole'}, - {name: 'Portsmouth'}, - {name: 'Powys'}, - {name: 'Reading'}, - {name: 'Redcar and Cleveland'}, - {name: 'Rutland'}, - {name: 'Scottish Borders'}, - {name: 'Shropshire'}, - {name: 'Slough'}, - {name: 'Somerset'}, - {name: 'South Glamorgan'}, - {name: 'South Gloucestershire'}, - {name: 'South Yorkshire'}, - {name: 'Southampton'}, - {name: 'Southend-on-Sea'}, - {name: 'Staffordshire'}, - {name: 'Stirlingshire'}, - {name: 'Stockton-on-Tees'}, - {name: 'Stoke-on-Trent'}, - {name: 'Strathclyde'}, - {name: 'Suffolk'}, - {name: 'Surrey'}, - {name: 'Swindon'}, - {name: 'Telford and Wrekin'}, - {name: 'Thurrock'}, - {name: 'Torbay'}, - {name: 'Tyne and Wear'}, - {name: 'Warrington'}, - {name: 'Warwickshire'}, - {name: 'West Berkshire'}, - {name: 'West Glamorgan'}, - {name: 'West Lothian'}, - {name: 'West Midlands'}, - {name: 'West Sussex'}, - {name: 'West Yorkshire'}, - {name: 'Western Isles'}, - {name: 'Wiltshire'}, - {name: 'Windsor and Maidenhead'}, - {name: 'Wokingham'}, - {name: 'Worcestershire'}, - {name: 'Wrexham'}, - {name: 'York'}] - }, - provinces: { - "ca": [ - {name: 'Alberta', abbreviation: 'AB'}, - {name: 'British Columbia', abbreviation: 'BC'}, - {name: 'Manitoba', abbreviation: 'MB'}, - {name: 'New Brunswick', abbreviation: 'NB'}, - {name: 'Newfoundland and Labrador', abbreviation: 'NL'}, - {name: 'Nova Scotia', abbreviation: 'NS'}, - {name: 'Ontario', abbreviation: 'ON'}, - {name: 'Prince Edward Island', abbreviation: 'PE'}, - {name: 'Quebec', abbreviation: 'QC'}, - {name: 'Saskatchewan', abbreviation: 'SK'}, - - // The case could be made that the following are not actually provinces - // since they are technically considered "territories" however they all - // look the same on an envelope! - {name: 'Northwest Territories', abbreviation: 'NT'}, - {name: 'Nunavut', abbreviation: 'NU'}, - {name: 'Yukon', abbreviation: 'YT'} - ], - "it": [ - { name: "Agrigento", abbreviation: "AG", code: 84 }, - { name: "Alessandria", abbreviation: "AL", code: 6 }, - { name: "Ancona", abbreviation: "AN", code: 42 }, - { name: "Aosta", abbreviation: "AO", code: 7 }, - { name: "L'Aquila", abbreviation: "AQ", code: 66 }, - { name: "Arezzo", abbreviation: "AR", code: 51 }, - { name: "Ascoli-Piceno", abbreviation: "AP", code: 44 }, - { name: "Asti", abbreviation: "AT", code: 5 }, - { name: "Avellino", abbreviation: "AV", code: 64 }, - { name: "Bari", abbreviation: "BA", code: 72 }, - { name: "Barletta-Andria-Trani", abbreviation: "BT", code: 72 }, - { name: "Belluno", abbreviation: "BL", code: 25 }, - { name: "Benevento", abbreviation: "BN", code: 62 }, - { name: "Bergamo", abbreviation: "BG", code: 16 }, - { name: "Biella", abbreviation: "BI", code: 96 }, - { name: "Bologna", abbreviation: "BO", code: 37 }, - { name: "Bolzano", abbreviation: "BZ", code: 21 }, - { name: "Brescia", abbreviation: "BS", code: 17 }, - { name: "Brindisi", abbreviation: "BR", code: 74 }, - { name: "Cagliari", abbreviation: "CA", code: 92 }, - { name: "Caltanissetta", abbreviation: "CL", code: 85 }, - { name: "Campobasso", abbreviation: "CB", code: 70 }, - { name: "Carbonia Iglesias", abbreviation: "CI", code: 70 }, - { name: "Caserta", abbreviation: "CE", code: 61 }, - { name: "Catania", abbreviation: "CT", code: 87 }, - { name: "Catanzaro", abbreviation: "CZ", code: 79 }, - { name: "Chieti", abbreviation: "CH", code: 69 }, - { name: "Como", abbreviation: "CO", code: 13 }, - { name: "Cosenza", abbreviation: "CS", code: 78 }, - { name: "Cremona", abbreviation: "CR", code: 19 }, - { name: "Crotone", abbreviation: "KR", code: 101 }, - { name: "Cuneo", abbreviation: "CN", code: 4 }, - { name: "Enna", abbreviation: "EN", code: 86 }, - { name: "Fermo", abbreviation: "FM", code: 86 }, - { name: "Ferrara", abbreviation: "FE", code: 38 }, - { name: "Firenze", abbreviation: "FI", code: 48 }, - { name: "Foggia", abbreviation: "FG", code: 71 }, - { name: "Forli-Cesena", abbreviation: "FC", code: 71 }, - { name: "Frosinone", abbreviation: "FR", code: 60 }, - { name: "Genova", abbreviation: "GE", code: 10 }, - { name: "Gorizia", abbreviation: "GO", code: 31 }, - { name: "Grosseto", abbreviation: "GR", code: 53 }, - { name: "Imperia", abbreviation: "IM", code: 8 }, - { name: "Isernia", abbreviation: "IS", code: 94 }, - { name: "La-Spezia", abbreviation: "SP", code: 66 }, - { name: "Latina", abbreviation: "LT", code: 59 }, - { name: "Lecce", abbreviation: "LE", code: 75 }, - { name: "Lecco", abbreviation: "LC", code: 97 }, - { name: "Livorno", abbreviation: "LI", code: 49 }, - { name: "Lodi", abbreviation: "LO", code: 98 }, - { name: "Lucca", abbreviation: "LU", code: 46 }, - { name: "Macerata", abbreviation: "MC", code: 43 }, - { name: "Mantova", abbreviation: "MN", code: 20 }, - { name: "Massa-Carrara", abbreviation: "MS", code: 45 }, - { name: "Matera", abbreviation: "MT", code: 77 }, - { name: "Medio Campidano", abbreviation: "VS", code: 77 }, - { name: "Messina", abbreviation: "ME", code: 83 }, - { name: "Milano", abbreviation: "MI", code: 15 }, - { name: "Modena", abbreviation: "MO", code: 36 }, - { name: "Monza-Brianza", abbreviation: "MB", code: 36 }, - { name: "Napoli", abbreviation: "NA", code: 63 }, - { name: "Novara", abbreviation: "NO", code: 3 }, - { name: "Nuoro", abbreviation: "NU", code: 91 }, - { name: "Ogliastra", abbreviation: "OG", code: 91 }, - { name: "Olbia Tempio", abbreviation: "OT", code: 91 }, - { name: "Oristano", abbreviation: "OR", code: 95 }, - { name: "Padova", abbreviation: "PD", code: 28 }, - { name: "Palermo", abbreviation: "PA", code: 82 }, - { name: "Parma", abbreviation: "PR", code: 34 }, - { name: "Pavia", abbreviation: "PV", code: 18 }, - { name: "Perugia", abbreviation: "PG", code: 54 }, - { name: "Pesaro-Urbino", abbreviation: "PU", code: 41 }, - { name: "Pescara", abbreviation: "PE", code: 68 }, - { name: "Piacenza", abbreviation: "PC", code: 33 }, - { name: "Pisa", abbreviation: "PI", code: 50 }, - { name: "Pistoia", abbreviation: "PT", code: 47 }, - { name: "Pordenone", abbreviation: "PN", code: 93 }, - { name: "Potenza", abbreviation: "PZ", code: 76 }, - { name: "Prato", abbreviation: "PO", code: 100 }, - { name: "Ragusa", abbreviation: "RG", code: 88 }, - { name: "Ravenna", abbreviation: "RA", code: 39 }, - { name: "Reggio-Calabria", abbreviation: "RC", code: 35 }, - { name: "Reggio-Emilia", abbreviation: "RE", code: 35 }, - { name: "Rieti", abbreviation: "RI", code: 57 }, - { name: "Rimini", abbreviation: "RN", code: 99 }, - { name: "Roma", abbreviation: "Roma", code: 58 }, - { name: "Rovigo", abbreviation: "RO", code: 29 }, - { name: "Salerno", abbreviation: "SA", code: 65 }, - { name: "Sassari", abbreviation: "SS", code: 90 }, - { name: "Savona", abbreviation: "SV", code: 9 }, - { name: "Siena", abbreviation: "SI", code: 52 }, - { name: "Siracusa", abbreviation: "SR", code: 89 }, - { name: "Sondrio", abbreviation: "SO", code: 14 }, - { name: "Taranto", abbreviation: "TA", code: 73 }, - { name: "Teramo", abbreviation: "TE", code: 67 }, - { name: "Terni", abbreviation: "TR", code: 55 }, - { name: "Torino", abbreviation: "TO", code: 1 }, - { name: "Trapani", abbreviation: "TP", code: 81 }, - { name: "Trento", abbreviation: "TN", code: 22 }, - { name: "Treviso", abbreviation: "TV", code: 26 }, - { name: "Trieste", abbreviation: "TS", code: 32 }, - { name: "Udine", abbreviation: "UD", code: 30 }, - { name: "Varese", abbreviation: "VA", code: 12 }, - { name: "Venezia", abbreviation: "VE", code: 27 }, - { name: "Verbania", abbreviation: "VB", code: 27 }, - { name: "Vercelli", abbreviation: "VC", code: 2 }, - { name: "Verona", abbreviation: "VR", code: 23 }, - { name: "Vibo-Valentia", abbreviation: "VV", code: 102 }, - { name: "Vicenza", abbreviation: "VI", code: 24 }, - { name: "Viterbo", abbreviation: "VT", code: 56 } - ] - }, - - // from: https://github.com/samsargent/Useful-Autocomplete-Data/blob/master/data/nationalities.json - nationalities: [ - {name: 'Afghan'}, - {name: 'Albanian'}, - {name: 'Algerian'}, - {name: 'American'}, - {name: 'Andorran'}, - {name: 'Angolan'}, - {name: 'Antiguans'}, - {name: 'Argentinean'}, - {name: 'Armenian'}, - {name: 'Australian'}, - {name: 'Austrian'}, - {name: 'Azerbaijani'}, - {name: 'Bahami'}, - {name: 'Bahraini'}, - {name: 'Bangladeshi'}, - {name: 'Barbadian'}, - {name: 'Barbudans'}, - {name: 'Batswana'}, - {name: 'Belarusian'}, - {name: 'Belgian'}, - {name: 'Belizean'}, - {name: 'Beninese'}, - {name: 'Bhutanese'}, - {name: 'Bolivian'}, - {name: 'Bosnian'}, - {name: 'Brazilian'}, - {name: 'British'}, - {name: 'Bruneian'}, - {name: 'Bulgarian'}, - {name: 'Burkinabe'}, - {name: 'Burmese'}, - {name: 'Burundian'}, - {name: 'Cambodian'}, - {name: 'Cameroonian'}, - {name: 'Canadian'}, - {name: 'Cape Verdean'}, - {name: 'Central African'}, - {name: 'Chadian'}, - {name: 'Chilean'}, - {name: 'Chinese'}, - {name: 'Colombian'}, - {name: 'Comoran'}, - {name: 'Congolese'}, - {name: 'Costa Rican'}, - {name: 'Croatian'}, - {name: 'Cuban'}, - {name: 'Cypriot'}, - {name: 'Czech'}, - {name: 'Danish'}, - {name: 'Djibouti'}, - {name: 'Dominican'}, - {name: 'Dutch'}, - {name: 'East Timorese'}, - {name: 'Ecuadorean'}, - {name: 'Egyptian'}, - {name: 'Emirian'}, - {name: 'Equatorial Guinean'}, - {name: 'Eritrean'}, - {name: 'Estonian'}, - {name: 'Ethiopian'}, - {name: 'Fijian'}, - {name: 'Filipino'}, - {name: 'Finnish'}, - {name: 'French'}, - {name: 'Gabonese'}, - {name: 'Gambian'}, - {name: 'Georgian'}, - {name: 'German'}, - {name: 'Ghanaian'}, - {name: 'Greek'}, - {name: 'Grenadian'}, - {name: 'Guatemalan'}, - {name: 'Guinea-Bissauan'}, - {name: 'Guinean'}, - {name: 'Guyanese'}, - {name: 'Haitian'}, - {name: 'Herzegovinian'}, - {name: 'Honduran'}, - {name: 'Hungarian'}, - {name: 'I-Kiribati'}, - {name: 'Icelander'}, - {name: 'Indian'}, - {name: 'Indonesian'}, - {name: 'Iranian'}, - {name: 'Iraqi'}, - {name: 'Irish'}, - {name: 'Israeli'}, - {name: 'Italian'}, - {name: 'Ivorian'}, - {name: 'Jamaican'}, - {name: 'Japanese'}, - {name: 'Jordanian'}, - {name: 'Kazakhstani'}, - {name: 'Kenyan'}, - {name: 'Kittian and Nevisian'}, - {name: 'Kuwaiti'}, - {name: 'Kyrgyz'}, - {name: 'Laotian'}, - {name: 'Latvian'}, - {name: 'Lebanese'}, - {name: 'Liberian'}, - {name: 'Libyan'}, - {name: 'Liechtensteiner'}, - {name: 'Lithuanian'}, - {name: 'Luxembourger'}, - {name: 'Macedonian'}, - {name: 'Malagasy'}, - {name: 'Malawian'}, - {name: 'Malaysian'}, - {name: 'Maldivan'}, - {name: 'Malian'}, - {name: 'Maltese'}, - {name: 'Marshallese'}, - {name: 'Mauritanian'}, - {name: 'Mauritian'}, - {name: 'Mexican'}, - {name: 'Micronesian'}, - {name: 'Moldovan'}, - {name: 'Monacan'}, - {name: 'Mongolian'}, - {name: 'Moroccan'}, - {name: 'Mosotho'}, - {name: 'Motswana'}, - {name: 'Mozambican'}, - {name: 'Namibian'}, - {name: 'Nauruan'}, - {name: 'Nepalese'}, - {name: 'New Zealander'}, - {name: 'Nicaraguan'}, - {name: 'Nigerian'}, - {name: 'Nigerien'}, - {name: 'North Korean'}, - {name: 'Northern Irish'}, - {name: 'Norwegian'}, - {name: 'Omani'}, - {name: 'Pakistani'}, - {name: 'Palauan'}, - {name: 'Panamanian'}, - {name: 'Papua New Guinean'}, - {name: 'Paraguayan'}, - {name: 'Peruvian'}, - {name: 'Polish'}, - {name: 'Portuguese'}, - {name: 'Qatari'}, - {name: 'Romani'}, - {name: 'Russian'}, - {name: 'Rwandan'}, - {name: 'Saint Lucian'}, - {name: 'Salvadoran'}, - {name: 'Samoan'}, - {name: 'San Marinese'}, - {name: 'Sao Tomean'}, - {name: 'Saudi'}, - {name: 'Scottish'}, - {name: 'Senegalese'}, - {name: 'Serbian'}, - {name: 'Seychellois'}, - {name: 'Sierra Leonean'}, - {name: 'Singaporean'}, - {name: 'Slovakian'}, - {name: 'Slovenian'}, - {name: 'Solomon Islander'}, - {name: 'Somali'}, - {name: 'South African'}, - {name: 'South Korean'}, - {name: 'Spanish'}, - {name: 'Sri Lankan'}, - {name: 'Sudanese'}, - {name: 'Surinamer'}, - {name: 'Swazi'}, - {name: 'Swedish'}, - {name: 'Swiss'}, - {name: 'Syrian'}, - {name: 'Taiwanese'}, - {name: 'Tajik'}, - {name: 'Tanzanian'}, - {name: 'Thai'}, - {name: 'Togolese'}, - {name: 'Tongan'}, - {name: 'Trinidadian or Tobagonian'}, - {name: 'Tunisian'}, - {name: 'Turkish'}, - {name: 'Tuvaluan'}, - {name: 'Ugandan'}, - {name: 'Ukrainian'}, - {name: 'Uruguaya'}, - {name: 'Uzbekistani'}, - {name: 'Venezuela'}, - {name: 'Vietnamese'}, - {name: 'Wels'}, - {name: 'Yemenit'}, - {name: 'Zambia'}, - {name: 'Zimbabwe'}, - ], - // http://www.loc.gov/standards/iso639-2/php/code_list.php (ISO-639-1 codes) - locale_languages: [ - "aa", - "ab", - "ae", - "af", - "ak", - "am", - "an", - "ar", - "as", - "av", - "ay", - "az", - "ba", - "be", - "bg", - "bh", - "bi", - "bm", - "bn", - "bo", - "br", - "bs", - "ca", - "ce", - "ch", - "co", - "cr", - "cs", - "cu", - "cv", - "cy", - "da", - "de", - "dv", - "dz", - "ee", - "el", - "en", - "eo", - "es", - "et", - "eu", - "fa", - "ff", - "fi", - "fj", - "fo", - "fr", - "fy", - "ga", - "gd", - "gl", - "gn", - "gu", - "gv", - "ha", - "he", - "hi", - "ho", - "hr", - "ht", - "hu", - "hy", - "hz", - "ia", - "id", - "ie", - "ig", - "ii", - "ik", - "io", - "is", - "it", - "iu", - "ja", - "jv", - "ka", - "kg", - "ki", - "kj", - "kk", - "kl", - "km", - "kn", - "ko", - "kr", - "ks", - "ku", - "kv", - "kw", - "ky", - "la", - "lb", - "lg", - "li", - "ln", - "lo", - "lt", - "lu", - "lv", - "mg", - "mh", - "mi", - "mk", - "ml", - "mn", - "mr", - "ms", - "mt", - "my", - "na", - "nb", - "nd", - "ne", - "ng", - "nl", - "nn", - "no", - "nr", - "nv", - "ny", - "oc", - "oj", - "om", - "or", - "os", - "pa", - "pi", - "pl", - "ps", - "pt", - "qu", - "rm", - "rn", - "ro", - "ru", - "rw", - "sa", - "sc", - "sd", - "se", - "sg", - "si", - "sk", - "sl", - "sm", - "sn", - "so", - "sq", - "sr", - "ss", - "st", - "su", - "sv", - "sw", - "ta", - "te", - "tg", - "th", - "ti", - "tk", - "tl", - "tn", - "to", - "tr", - "ts", - "tt", - "tw", - "ty", - "ug", - "uk", - "ur", - "uz", - "ve", - "vi", - "vo", - "wa", - "wo", - "xh", - "yi", - "yo", - "za", - "zh", - "zu" - ], - - // From http://data.okfn.org/data/core/language-codes#resource-language-codes-full (IETF language tags) - locale_regions: [ - "agq-CM", - "asa-TZ", - "ast-ES", - "bas-CM", - "bem-ZM", - "bez-TZ", - "brx-IN", - "cgg-UG", - "chr-US", - "dav-KE", - "dje-NE", - "dsb-DE", - "dua-CM", - "dyo-SN", - "ebu-KE", - "ewo-CM", - "fil-PH", - "fur-IT", - "gsw-CH", - "gsw-FR", - "gsw-LI", - "guz-KE", - "haw-US", - "hsb-DE", - "jgo-CM", - "jmc-TZ", - "kab-DZ", - "kam-KE", - "kde-TZ", - "kea-CV", - "khq-ML", - "kkj-CM", - "kln-KE", - "kok-IN", - "ksb-TZ", - "ksf-CM", - "ksh-DE", - "lag-TZ", - "lkt-US", - "luo-KE", - "luy-KE", - "mas-KE", - "mas-TZ", - "mer-KE", - "mfe-MU", - "mgh-MZ", - "mgo-CM", - "mua-CM", - "naq-NA", - "nmg-CM", - "nnh-CM", - "nus-SD", - "nyn-UG", - "rof-TZ", - "rwk-TZ", - "sah-RU", - "saq-KE", - "sbp-TZ", - "seh-MZ", - "ses-ML", - "shi-Latn", - "shi-Latn-MA", - "shi-Tfng", - "shi-Tfng-MA", - "smn-FI", - "teo-KE", - "teo-UG", - "twq-NE", - "tzm-Latn", - "tzm-Latn-MA", - "vai-Latn", - "vai-Latn-LR", - "vai-Vaii", - "vai-Vaii-LR", - "vun-TZ", - "wae-CH", - "xog-UG", - "yav-CM", - "zgh-MA", - "af-NA", - "af-ZA", - "ak-GH", - "am-ET", - "ar-001", - "ar-AE", - "ar-BH", - "ar-DJ", - "ar-DZ", - "ar-EG", - "ar-EH", - "ar-ER", - "ar-IL", - "ar-IQ", - "ar-JO", - "ar-KM", - "ar-KW", - "ar-LB", - "ar-LY", - "ar-MA", - "ar-MR", - "ar-OM", - "ar-PS", - "ar-QA", - "ar-SA", - "ar-SD", - "ar-SO", - "ar-SS", - "ar-SY", - "ar-TD", - "ar-TN", - "ar-YE", - "as-IN", - "az-Cyrl", - "az-Cyrl-AZ", - "az-Latn", - "az-Latn-AZ", - "be-BY", - "bg-BG", - "bm-Latn", - "bm-Latn-ML", - "bn-BD", - "bn-IN", - "bo-CN", - "bo-IN", - "br-FR", - "bs-Cyrl", - "bs-Cyrl-BA", - "bs-Latn", - "bs-Latn-BA", - "ca-AD", - "ca-ES", - "ca-ES-VALENCIA", - "ca-FR", - "ca-IT", - "cs-CZ", - "cy-GB", - "da-DK", - "da-GL", - "de-AT", - "de-BE", - "de-CH", - "de-DE", - "de-LI", - "de-LU", - "dz-BT", - "ee-GH", - "ee-TG", - "el-CY", - "el-GR", - "en-001", - "en-150", - "en-AG", - "en-AI", - "en-AS", - "en-AU", - "en-BB", - "en-BE", - "en-BM", - "en-BS", - "en-BW", - "en-BZ", - "en-CA", - "en-CC", - "en-CK", - "en-CM", - "en-CX", - "en-DG", - "en-DM", - "en-ER", - "en-FJ", - "en-FK", - "en-FM", - "en-GB", - "en-GD", - "en-GG", - "en-GH", - "en-GI", - "en-GM", - "en-GU", - "en-GY", - "en-HK", - "en-IE", - "en-IM", - "en-IN", - "en-IO", - "en-JE", - "en-JM", - "en-KE", - "en-KI", - "en-KN", - "en-KY", - "en-LC", - "en-LR", - "en-LS", - "en-MG", - "en-MH", - "en-MO", - "en-MP", - "en-MS", - "en-MT", - "en-MU", - "en-MW", - "en-MY", - "en-NA", - "en-NF", - "en-NG", - "en-NR", - "en-NU", - "en-NZ", - "en-PG", - "en-PH", - "en-PK", - "en-PN", - "en-PR", - "en-PW", - "en-RW", - "en-SB", - "en-SC", - "en-SD", - "en-SG", - "en-SH", - "en-SL", - "en-SS", - "en-SX", - "en-SZ", - "en-TC", - "en-TK", - "en-TO", - "en-TT", - "en-TV", - "en-TZ", - "en-UG", - "en-UM", - "en-US", - "en-US-POSIX", - "en-VC", - "en-VG", - "en-VI", - "en-VU", - "en-WS", - "en-ZA", - "en-ZM", - "en-ZW", - "eo-001", - "es-419", - "es-AR", - "es-BO", - "es-CL", - "es-CO", - "es-CR", - "es-CU", - "es-DO", - "es-EA", - "es-EC", - "es-ES", - "es-GQ", - "es-GT", - "es-HN", - "es-IC", - "es-MX", - "es-NI", - "es-PA", - "es-PE", - "es-PH", - "es-PR", - "es-PY", - "es-SV", - "es-US", - "es-UY", - "es-VE", - "et-EE", - "eu-ES", - "fa-AF", - "fa-IR", - "ff-CM", - "ff-GN", - "ff-MR", - "ff-SN", - "fi-FI", - "fo-FO", - "fr-BE", - "fr-BF", - "fr-BI", - "fr-BJ", - "fr-BL", - "fr-CA", - "fr-CD", - "fr-CF", - "fr-CG", - "fr-CH", - "fr-CI", - "fr-CM", - "fr-DJ", - "fr-DZ", - "fr-FR", - "fr-GA", - "fr-GF", - "fr-GN", - "fr-GP", - "fr-GQ", - "fr-HT", - "fr-KM", - "fr-LU", - "fr-MA", - "fr-MC", - "fr-MF", - "fr-MG", - "fr-ML", - "fr-MQ", - "fr-MR", - "fr-MU", - "fr-NC", - "fr-NE", - "fr-PF", - "fr-PM", - "fr-RE", - "fr-RW", - "fr-SC", - "fr-SN", - "fr-SY", - "fr-TD", - "fr-TG", - "fr-TN", - "fr-VU", - "fr-WF", - "fr-YT", - "fy-NL", - "ga-IE", - "gd-GB", - "gl-ES", - "gu-IN", - "gv-IM", - "ha-Latn", - "ha-Latn-GH", - "ha-Latn-NE", - "ha-Latn-NG", - "he-IL", - "hi-IN", - "hr-BA", - "hr-HR", - "hu-HU", - "hy-AM", - "id-ID", - "ig-NG", - "ii-CN", - "is-IS", - "it-CH", - "it-IT", - "it-SM", - "ja-JP", - "ka-GE", - "ki-KE", - "kk-Cyrl", - "kk-Cyrl-KZ", - "kl-GL", - "km-KH", - "kn-IN", - "ko-KP", - "ko-KR", - "ks-Arab", - "ks-Arab-IN", - "kw-GB", - "ky-Cyrl", - "ky-Cyrl-KG", - "lb-LU", - "lg-UG", - "ln-AO", - "ln-CD", - "ln-CF", - "ln-CG", - "lo-LA", - "lt-LT", - "lu-CD", - "lv-LV", - "mg-MG", - "mk-MK", - "ml-IN", - "mn-Cyrl", - "mn-Cyrl-MN", - "mr-IN", - "ms-Latn", - "ms-Latn-BN", - "ms-Latn-MY", - "ms-Latn-SG", - "mt-MT", - "my-MM", - "nb-NO", - "nb-SJ", - "nd-ZW", - "ne-IN", - "ne-NP", - "nl-AW", - "nl-BE", - "nl-BQ", - "nl-CW", - "nl-NL", - "nl-SR", - "nl-SX", - "nn-NO", - "om-ET", - "om-KE", - "or-IN", - "os-GE", - "os-RU", - "pa-Arab", - "pa-Arab-PK", - "pa-Guru", - "pa-Guru-IN", - "pl-PL", - "ps-AF", - "pt-AO", - "pt-BR", - "pt-CV", - "pt-GW", - "pt-MO", - "pt-MZ", - "pt-PT", - "pt-ST", - "pt-TL", - "qu-BO", - "qu-EC", - "qu-PE", - "rm-CH", - "rn-BI", - "ro-MD", - "ro-RO", - "ru-BY", - "ru-KG", - "ru-KZ", - "ru-MD", - "ru-RU", - "ru-UA", - "rw-RW", - "se-FI", - "se-NO", - "se-SE", - "sg-CF", - "si-LK", - "sk-SK", - "sl-SI", - "sn-ZW", - "so-DJ", - "so-ET", - "so-KE", - "so-SO", - "sq-AL", - "sq-MK", - "sq-XK", - "sr-Cyrl", - "sr-Cyrl-BA", - "sr-Cyrl-ME", - "sr-Cyrl-RS", - "sr-Cyrl-XK", - "sr-Latn", - "sr-Latn-BA", - "sr-Latn-ME", - "sr-Latn-RS", - "sr-Latn-XK", - "sv-AX", - "sv-FI", - "sv-SE", - "sw-CD", - "sw-KE", - "sw-TZ", - "sw-UG", - "ta-IN", - "ta-LK", - "ta-MY", - "ta-SG", - "te-IN", - "th-TH", - "ti-ER", - "ti-ET", - "to-TO", - "tr-CY", - "tr-TR", - "ug-Arab", - "ug-Arab-CN", - "uk-UA", - "ur-IN", - "ur-PK", - "uz-Arab", - "uz-Arab-AF", - "uz-Cyrl", - "uz-Cyrl-UZ", - "uz-Latn", - "uz-Latn-UZ", - "vi-VN", - "yi-001", - "yo-BJ", - "yo-NG", - "zh-Hans", - "zh-Hans-CN", - "zh-Hans-HK", - "zh-Hans-MO", - "zh-Hans-SG", - "zh-Hant", - "zh-Hant-HK", - "zh-Hant-MO", - "zh-Hant-TW", - "zu-ZA" - ], - - us_states_and_dc: [ - {name: 'Alabama', abbreviation: 'AL'}, - {name: 'Alaska', abbreviation: 'AK'}, - {name: 'Arizona', abbreviation: 'AZ'}, - {name: 'Arkansas', abbreviation: 'AR'}, - {name: 'California', abbreviation: 'CA'}, - {name: 'Colorado', abbreviation: 'CO'}, - {name: 'Connecticut', abbreviation: 'CT'}, - {name: 'Delaware', abbreviation: 'DE'}, - {name: 'District of Columbia', abbreviation: 'DC'}, - {name: 'Florida', abbreviation: 'FL'}, - {name: 'Georgia', abbreviation: 'GA'}, - {name: 'Hawaii', abbreviation: 'HI'}, - {name: 'Idaho', abbreviation: 'ID'}, - {name: 'Illinois', abbreviation: 'IL'}, - {name: 'Indiana', abbreviation: 'IN'}, - {name: 'Iowa', abbreviation: 'IA'}, - {name: 'Kansas', abbreviation: 'KS'}, - {name: 'Kentucky', abbreviation: 'KY'}, - {name: 'Louisiana', abbreviation: 'LA'}, - {name: 'Maine', abbreviation: 'ME'}, - {name: 'Maryland', abbreviation: 'MD'}, - {name: 'Massachusetts', abbreviation: 'MA'}, - {name: 'Michigan', abbreviation: 'MI'}, - {name: 'Minnesota', abbreviation: 'MN'}, - {name: 'Mississippi', abbreviation: 'MS'}, - {name: 'Missouri', abbreviation: 'MO'}, - {name: 'Montana', abbreviation: 'MT'}, - {name: 'Nebraska', abbreviation: 'NE'}, - {name: 'Nevada', abbreviation: 'NV'}, - {name: 'New Hampshire', abbreviation: 'NH'}, - {name: 'New Jersey', abbreviation: 'NJ'}, - {name: 'New Mexico', abbreviation: 'NM'}, - {name: 'New York', abbreviation: 'NY'}, - {name: 'North Carolina', abbreviation: 'NC'}, - {name: 'North Dakota', abbreviation: 'ND'}, - {name: 'Ohio', abbreviation: 'OH'}, - {name: 'Oklahoma', abbreviation: 'OK'}, - {name: 'Oregon', abbreviation: 'OR'}, - {name: 'Pennsylvania', abbreviation: 'PA'}, - {name: 'Rhode Island', abbreviation: 'RI'}, - {name: 'South Carolina', abbreviation: 'SC'}, - {name: 'South Dakota', abbreviation: 'SD'}, - {name: 'Tennessee', abbreviation: 'TN'}, - {name: 'Texas', abbreviation: 'TX'}, - {name: 'Utah', abbreviation: 'UT'}, - {name: 'Vermont', abbreviation: 'VT'}, - {name: 'Virginia', abbreviation: 'VA'}, - {name: 'Washington', abbreviation: 'WA'}, - {name: 'West Virginia', abbreviation: 'WV'}, - {name: 'Wisconsin', abbreviation: 'WI'}, - {name: 'Wyoming', abbreviation: 'WY'} - ], - - territories: [ - {name: 'American Samoa', abbreviation: 'AS'}, - {name: 'Federated States of Micronesia', abbreviation: 'FM'}, - {name: 'Guam', abbreviation: 'GU'}, - {name: 'Marshall Islands', abbreviation: 'MH'}, - {name: 'Northern Mariana Islands', abbreviation: 'MP'}, - {name: 'Puerto Rico', abbreviation: 'PR'}, - {name: 'Virgin Islands, U.S.', abbreviation: 'VI'} - ], - - armed_forces: [ - {name: 'Armed Forces Europe', abbreviation: 'AE'}, - {name: 'Armed Forces Pacific', abbreviation: 'AP'}, - {name: 'Armed Forces the Americas', abbreviation: 'AA'} - ], - - country_regions: { - it: [ - { name: "Valle d'Aosta", abbreviation: "VDA" }, - { name: "Piemonte", abbreviation: "PIE" }, - { name: "Lombardia", abbreviation: "LOM" }, - { name: "Veneto", abbreviation: "VEN" }, - { name: "Trentino Alto Adige", abbreviation: "TAA" }, - { name: "Friuli Venezia Giulia", abbreviation: "FVG" }, - { name: "Liguria", abbreviation: "LIG" }, - { name: "Emilia Romagna", abbreviation: "EMR" }, - { name: "Toscana", abbreviation: "TOS" }, - { name: "Umbria", abbreviation: "UMB" }, - { name: "Marche", abbreviation: "MAR" }, - { name: "Abruzzo", abbreviation: "ABR" }, - { name: "Lazio", abbreviation: "LAZ" }, - { name: "Campania", abbreviation: "CAM" }, - { name: "Puglia", abbreviation: "PUG" }, - { name: "Basilicata", abbreviation: "BAS" }, - { name: "Molise", abbreviation: "MOL" }, - { name: "Calabria", abbreviation: "CAL" }, - { name: "Sicilia", abbreviation: "SIC" }, - { name: "Sardegna", abbreviation: "SAR" } - ], - mx: [ - { name: 'Aguascalientes', abbreviation: 'AGU' }, - { name: 'Baja California', abbreviation: 'BCN' }, - { name: 'Baja California Sur', abbreviation: 'BCS' }, - { name: 'Campeche', abbreviation: 'CAM' }, - { name: 'Chiapas', abbreviation: 'CHP' }, - { name: 'Chihuahua', abbreviation: 'CHH' }, - { name: 'Ciudad de México', abbreviation: 'DIF' }, - { name: 'Coahuila', abbreviation: 'COA' }, - { name: 'Colima', abbreviation: 'COL' }, - { name: 'Durango', abbreviation: 'DUR' }, - { name: 'Guanajuato', abbreviation: 'GUA' }, - { name: 'Guerrero', abbreviation: 'GRO' }, - { name: 'Hidalgo', abbreviation: 'HID' }, - { name: 'Jalisco', abbreviation: 'JAL' }, - { name: 'México', abbreviation: 'MEX' }, - { name: 'Michoacán', abbreviation: 'MIC' }, - { name: 'Morelos', abbreviation: 'MOR' }, - { name: 'Nayarit', abbreviation: 'NAY' }, - { name: 'Nuevo León', abbreviation: 'NLE' }, - { name: 'Oaxaca', abbreviation: 'OAX' }, - { name: 'Puebla', abbreviation: 'PUE' }, - { name: 'Querétaro', abbreviation: 'QUE' }, - { name: 'Quintana Roo', abbreviation: 'ROO' }, - { name: 'San Luis Potosí', abbreviation: 'SLP' }, - { name: 'Sinaloa', abbreviation: 'SIN' }, - { name: 'Sonora', abbreviation: 'SON' }, - { name: 'Tabasco', abbreviation: 'TAB' }, - { name: 'Tamaulipas', abbreviation: 'TAM' }, - { name: 'Tlaxcala', abbreviation: 'TLA' }, - { name: 'Veracruz', abbreviation: 'VER' }, - { name: 'Yucatán', abbreviation: 'YUC' }, - { name: 'Zacatecas', abbreviation: 'ZAC' } - ] - }, - - street_suffixes: { - 'us': [ - {name: 'Avenue', abbreviation: 'Ave'}, - {name: 'Boulevard', abbreviation: 'Blvd'}, - {name: 'Center', abbreviation: 'Ctr'}, - {name: 'Circle', abbreviation: 'Cir'}, - {name: 'Court', abbreviation: 'Ct'}, - {name: 'Drive', abbreviation: 'Dr'}, - {name: 'Extension', abbreviation: 'Ext'}, - {name: 'Glen', abbreviation: 'Gln'}, - {name: 'Grove', abbreviation: 'Grv'}, - {name: 'Heights', abbreviation: 'Hts'}, - {name: 'Highway', abbreviation: 'Hwy'}, - {name: 'Junction', abbreviation: 'Jct'}, - {name: 'Key', abbreviation: 'Key'}, - {name: 'Lane', abbreviation: 'Ln'}, - {name: 'Loop', abbreviation: 'Loop'}, - {name: 'Manor', abbreviation: 'Mnr'}, - {name: 'Mill', abbreviation: 'Mill'}, - {name: 'Park', abbreviation: 'Park'}, - {name: 'Parkway', abbreviation: 'Pkwy'}, - {name: 'Pass', abbreviation: 'Pass'}, - {name: 'Path', abbreviation: 'Path'}, - {name: 'Pike', abbreviation: 'Pike'}, - {name: 'Place', abbreviation: 'Pl'}, - {name: 'Plaza', abbreviation: 'Plz'}, - {name: 'Point', abbreviation: 'Pt'}, - {name: 'Ridge', abbreviation: 'Rdg'}, - {name: 'River', abbreviation: 'Riv'}, - {name: 'Road', abbreviation: 'Rd'}, - {name: 'Square', abbreviation: 'Sq'}, - {name: 'Street', abbreviation: 'St'}, - {name: 'Terrace', abbreviation: 'Ter'}, - {name: 'Trail', abbreviation: 'Trl'}, - {name: 'Turnpike', abbreviation: 'Tpke'}, - {name: 'View', abbreviation: 'Vw'}, - {name: 'Way', abbreviation: 'Way'} - ], - 'it': [ - { name: 'Accesso', abbreviation: 'Acc.' }, - { name: 'Alzaia', abbreviation: 'Alz.' }, - { name: 'Arco', abbreviation: 'Arco' }, - { name: 'Archivolto', abbreviation: 'Acv.' }, - { name: 'Arena', abbreviation: 'Arena' }, - { name: 'Argine', abbreviation: 'Argine' }, - { name: 'Bacino', abbreviation: 'Bacino' }, - { name: 'Banchi', abbreviation: 'Banchi' }, - { name: 'Banchina', abbreviation: 'Ban.' }, - { name: 'Bastioni', abbreviation: 'Bas.' }, - { name: 'Belvedere', abbreviation: 'Belv.' }, - { name: 'Borgata', abbreviation: 'B.ta' }, - { name: 'Borgo', abbreviation: 'B.go' }, - { name: 'Calata', abbreviation: 'Cal.' }, - { name: 'Calle', abbreviation: 'Calle' }, - { name: 'Campiello', abbreviation: 'Cam.' }, - { name: 'Campo', abbreviation: 'Cam.' }, - { name: 'Canale', abbreviation: 'Can.' }, - { name: 'Carraia', abbreviation: 'Carr.' }, - { name: 'Cascina', abbreviation: 'Cascina' }, - { name: 'Case sparse', abbreviation: 'c.s.' }, - { name: 'Cavalcavia', abbreviation: 'Cv.' }, - { name: 'Circonvallazione', abbreviation: 'Cv.' }, - { name: 'Complanare', abbreviation: 'C.re' }, - { name: 'Contrada', abbreviation: 'C.da' }, - { name: 'Corso', abbreviation: 'C.so' }, - { name: 'Corte', abbreviation: 'C.te' }, - { name: 'Cortile', abbreviation: 'C.le' }, - { name: 'Diramazione', abbreviation: 'Dir.' }, - { name: 'Fondaco', abbreviation: 'F.co' }, - { name: 'Fondamenta', abbreviation: 'F.ta' }, - { name: 'Fondo', abbreviation: 'F.do' }, - { name: 'Frazione', abbreviation: 'Fr.' }, - { name: 'Isola', abbreviation: 'Is.' }, - { name: 'Largo', abbreviation: 'L.go' }, - { name: 'Litoranea', abbreviation: 'Lit.' }, - { name: 'Lungolago', abbreviation: 'L.go lago' }, - { name: 'Lungo Po', abbreviation: 'l.go Po' }, - { name: 'Molo', abbreviation: 'Molo' }, - { name: 'Mura', abbreviation: 'Mura' }, - { name: 'Passaggio privato', abbreviation: 'pass. priv.' }, - { name: 'Passeggiata', abbreviation: 'Pass.' }, - { name: 'Piazza', abbreviation: 'P.zza' }, - { name: 'Piazzale', abbreviation: 'P.le' }, - { name: 'Ponte', abbreviation: 'P.te' }, - { name: 'Portico', abbreviation: 'P.co' }, - { name: 'Rampa', abbreviation: 'Rampa' }, - { name: 'Regione', abbreviation: 'Reg.' }, - { name: 'Rione', abbreviation: 'R.ne' }, - { name: 'Rio', abbreviation: 'Rio' }, - { name: 'Ripa', abbreviation: 'Ripa' }, - { name: 'Riva', abbreviation: 'Riva' }, - { name: 'Rondò', abbreviation: 'Rondò' }, - { name: 'Rotonda', abbreviation: 'Rot.' }, - { name: 'Sagrato', abbreviation: 'Sagr.' }, - { name: 'Salita', abbreviation: 'Sal.' }, - { name: 'Scalinata', abbreviation: 'Scal.' }, - { name: 'Scalone', abbreviation: 'Scal.' }, - { name: 'Slargo', abbreviation: 'Sl.' }, - { name: 'Sottoportico', abbreviation: 'Sott.' }, - { name: 'Strada', abbreviation: 'Str.' }, - { name: 'Stradale', abbreviation: 'Str.le' }, - { name: 'Strettoia', abbreviation: 'Strett.' }, - { name: 'Traversa', abbreviation: 'Trav.' }, - { name: 'Via', abbreviation: 'V.' }, - { name: 'Viale', abbreviation: 'V.le' }, - { name: 'Vicinale', abbreviation: 'Vic.le' }, - { name: 'Vicolo', abbreviation: 'Vic.' } - ], - 'uk' : [ - {name: 'Avenue', abbreviation: 'Ave'}, - {name: 'Close', abbreviation: 'Cl'}, - {name: 'Court', abbreviation: 'Ct'}, - {name: 'Crescent', abbreviation: 'Cr'}, - {name: 'Drive', abbreviation: 'Dr'}, - {name: 'Garden', abbreviation: 'Gdn'}, - {name: 'Gardens', abbreviation: 'Gdns'}, - {name: 'Green', abbreviation: 'Gn'}, - {name: 'Grove', abbreviation: 'Gr'}, - {name: 'Lane', abbreviation: 'Ln'}, - {name: 'Mount', abbreviation: 'Mt'}, - {name: 'Place', abbreviation: 'Pl'}, - {name: 'Park', abbreviation: 'Pk'}, - {name: 'Ridge', abbreviation: 'Rdg'}, - {name: 'Road', abbreviation: 'Rd'}, - {name: 'Square', abbreviation: 'Sq'}, - {name: 'Street', abbreviation: 'St'}, - {name: 'Terrace', abbreviation: 'Ter'}, - {name: 'Valley', abbreviation: 'Val'} - ] - }, - - months: [ - {name: 'January', short_name: 'Jan', numeric: '01', days: 31}, - // Not messing with leap years... - {name: 'February', short_name: 'Feb', numeric: '02', days: 28}, - {name: 'March', short_name: 'Mar', numeric: '03', days: 31}, - {name: 'April', short_name: 'Apr', numeric: '04', days: 30}, - {name: 'May', short_name: 'May', numeric: '05', days: 31}, - {name: 'June', short_name: 'Jun', numeric: '06', days: 30}, - {name: 'July', short_name: 'Jul', numeric: '07', days: 31}, - {name: 'August', short_name: 'Aug', numeric: '08', days: 31}, - {name: 'September', short_name: 'Sep', numeric: '09', days: 30}, - {name: 'October', short_name: 'Oct', numeric: '10', days: 31}, - {name: 'November', short_name: 'Nov', numeric: '11', days: 30}, - {name: 'December', short_name: 'Dec', numeric: '12', days: 31} - ], - - // http://en.wikipedia.org/wiki/Bank_card_number#Issuer_identification_number_.28IIN.29 - cc_types: [ - {name: "American Express", short_name: 'amex', prefix: '34', length: 15}, - {name: "Bankcard", short_name: 'bankcard', prefix: '5610', length: 16}, - {name: "China UnionPay", short_name: 'chinaunion', prefix: '62', length: 16}, - {name: "Diners Club Carte Blanche", short_name: 'dccarte', prefix: '300', length: 14}, - {name: "Diners Club enRoute", short_name: 'dcenroute', prefix: '2014', length: 15}, - {name: "Diners Club International", short_name: 'dcintl', prefix: '36', length: 14}, - {name: "Diners Club United States & Canada", short_name: 'dcusc', prefix: '54', length: 16}, - {name: "Discover Card", short_name: 'discover', prefix: '6011', length: 16}, - {name: "InstaPayment", short_name: 'instapay', prefix: '637', length: 16}, - {name: "JCB", short_name: 'jcb', prefix: '3528', length: 16}, - {name: "Laser", short_name: 'laser', prefix: '6304', length: 16}, - {name: "Maestro", short_name: 'maestro', prefix: '5018', length: 16}, - {name: "Mastercard", short_name: 'mc', prefix: '51', length: 16}, - {name: "Solo", short_name: 'solo', prefix: '6334', length: 16}, - {name: "Switch", short_name: 'switch', prefix: '4903', length: 16}, - {name: "Visa", short_name: 'visa', prefix: '4', length: 16}, - {name: "Visa Electron", short_name: 'electron', prefix: '4026', length: 16} - ], - - //return all world currency by ISO 4217 - currency_types: [ - {'code' : 'AED', 'name' : 'United Arab Emirates Dirham'}, - {'code' : 'AFN', 'name' : 'Afghanistan Afghani'}, - {'code' : 'ALL', 'name' : 'Albania Lek'}, - {'code' : 'AMD', 'name' : 'Armenia Dram'}, - {'code' : 'ANG', 'name' : 'Netherlands Antilles Guilder'}, - {'code' : 'AOA', 'name' : 'Angola Kwanza'}, - {'code' : 'ARS', 'name' : 'Argentina Peso'}, - {'code' : 'AUD', 'name' : 'Australia Dollar'}, - {'code' : 'AWG', 'name' : 'Aruba Guilder'}, - {'code' : 'AZN', 'name' : 'Azerbaijan New Manat'}, - {'code' : 'BAM', 'name' : 'Bosnia and Herzegovina Convertible Marka'}, - {'code' : 'BBD', 'name' : 'Barbados Dollar'}, - {'code' : 'BDT', 'name' : 'Bangladesh Taka'}, - {'code' : 'BGN', 'name' : 'Bulgaria Lev'}, - {'code' : 'BHD', 'name' : 'Bahrain Dinar'}, - {'code' : 'BIF', 'name' : 'Burundi Franc'}, - {'code' : 'BMD', 'name' : 'Bermuda Dollar'}, - {'code' : 'BND', 'name' : 'Brunei Darussalam Dollar'}, - {'code' : 'BOB', 'name' : 'Bolivia Boliviano'}, - {'code' : 'BRL', 'name' : 'Brazil Real'}, - {'code' : 'BSD', 'name' : 'Bahamas Dollar'}, - {'code' : 'BTN', 'name' : 'Bhutan Ngultrum'}, - {'code' : 'BWP', 'name' : 'Botswana Pula'}, - {'code' : 'BYR', 'name' : 'Belarus Ruble'}, - {'code' : 'BZD', 'name' : 'Belize Dollar'}, - {'code' : 'CAD', 'name' : 'Canada Dollar'}, - {'code' : 'CDF', 'name' : 'Congo/Kinshasa Franc'}, - {'code' : 'CHF', 'name' : 'Switzerland Franc'}, - {'code' : 'CLP', 'name' : 'Chile Peso'}, - {'code' : 'CNY', 'name' : 'China Yuan Renminbi'}, - {'code' : 'COP', 'name' : 'Colombia Peso'}, - {'code' : 'CRC', 'name' : 'Costa Rica Colon'}, - {'code' : 'CUC', 'name' : 'Cuba Convertible Peso'}, - {'code' : 'CUP', 'name' : 'Cuba Peso'}, - {'code' : 'CVE', 'name' : 'Cape Verde Escudo'}, - {'code' : 'CZK', 'name' : 'Czech Republic Koruna'}, - {'code' : 'DJF', 'name' : 'Djibouti Franc'}, - {'code' : 'DKK', 'name' : 'Denmark Krone'}, - {'code' : 'DOP', 'name' : 'Dominican Republic Peso'}, - {'code' : 'DZD', 'name' : 'Algeria Dinar'}, - {'code' : 'EGP', 'name' : 'Egypt Pound'}, - {'code' : 'ERN', 'name' : 'Eritrea Nakfa'}, - {'code' : 'ETB', 'name' : 'Ethiopia Birr'}, - {'code' : 'EUR', 'name' : 'Euro Member Countries'}, - {'code' : 'FJD', 'name' : 'Fiji Dollar'}, - {'code' : 'FKP', 'name' : 'Falkland Islands (Malvinas) Pound'}, - {'code' : 'GBP', 'name' : 'United Kingdom Pound'}, - {'code' : 'GEL', 'name' : 'Georgia Lari'}, - {'code' : 'GGP', 'name' : 'Guernsey Pound'}, - {'code' : 'GHS', 'name' : 'Ghana Cedi'}, - {'code' : 'GIP', 'name' : 'Gibraltar Pound'}, - {'code' : 'GMD', 'name' : 'Gambia Dalasi'}, - {'code' : 'GNF', 'name' : 'Guinea Franc'}, - {'code' : 'GTQ', 'name' : 'Guatemala Quetzal'}, - {'code' : 'GYD', 'name' : 'Guyana Dollar'}, - {'code' : 'HKD', 'name' : 'Hong Kong Dollar'}, - {'code' : 'HNL', 'name' : 'Honduras Lempira'}, - {'code' : 'HRK', 'name' : 'Croatia Kuna'}, - {'code' : 'HTG', 'name' : 'Haiti Gourde'}, - {'code' : 'HUF', 'name' : 'Hungary Forint'}, - {'code' : 'IDR', 'name' : 'Indonesia Rupiah'}, - {'code' : 'ILS', 'name' : 'Israel Shekel'}, - {'code' : 'IMP', 'name' : 'Isle of Man Pound'}, - {'code' : 'INR', 'name' : 'India Rupee'}, - {'code' : 'IQD', 'name' : 'Iraq Dinar'}, - {'code' : 'IRR', 'name' : 'Iran Rial'}, - {'code' : 'ISK', 'name' : 'Iceland Krona'}, - {'code' : 'JEP', 'name' : 'Jersey Pound'}, - {'code' : 'JMD', 'name' : 'Jamaica Dollar'}, - {'code' : 'JOD', 'name' : 'Jordan Dinar'}, - {'code' : 'JPY', 'name' : 'Japan Yen'}, - {'code' : 'KES', 'name' : 'Kenya Shilling'}, - {'code' : 'KGS', 'name' : 'Kyrgyzstan Som'}, - {'code' : 'KHR', 'name' : 'Cambodia Riel'}, - {'code' : 'KMF', 'name' : 'Comoros Franc'}, - {'code' : 'KPW', 'name' : 'Korea (North) Won'}, - {'code' : 'KRW', 'name' : 'Korea (South) Won'}, - {'code' : 'KWD', 'name' : 'Kuwait Dinar'}, - {'code' : 'KYD', 'name' : 'Cayman Islands Dollar'}, - {'code' : 'KZT', 'name' : 'Kazakhstan Tenge'}, - {'code' : 'LAK', 'name' : 'Laos Kip'}, - {'code' : 'LBP', 'name' : 'Lebanon Pound'}, - {'code' : 'LKR', 'name' : 'Sri Lanka Rupee'}, - {'code' : 'LRD', 'name' : 'Liberia Dollar'}, - {'code' : 'LSL', 'name' : 'Lesotho Loti'}, - {'code' : 'LTL', 'name' : 'Lithuania Litas'}, - {'code' : 'LYD', 'name' : 'Libya Dinar'}, - {'code' : 'MAD', 'name' : 'Morocco Dirham'}, - {'code' : 'MDL', 'name' : 'Moldova Leu'}, - {'code' : 'MGA', 'name' : 'Madagascar Ariary'}, - {'code' : 'MKD', 'name' : 'Macedonia Denar'}, - {'code' : 'MMK', 'name' : 'Myanmar (Burma) Kyat'}, - {'code' : 'MNT', 'name' : 'Mongolia Tughrik'}, - {'code' : 'MOP', 'name' : 'Macau Pataca'}, - {'code' : 'MRO', 'name' : 'Mauritania Ouguiya'}, - {'code' : 'MUR', 'name' : 'Mauritius Rupee'}, - {'code' : 'MVR', 'name' : 'Maldives (Maldive Islands) Rufiyaa'}, - {'code' : 'MWK', 'name' : 'Malawi Kwacha'}, - {'code' : 'MXN', 'name' : 'Mexico Peso'}, - {'code' : 'MYR', 'name' : 'Malaysia Ringgit'}, - {'code' : 'MZN', 'name' : 'Mozambique Metical'}, - {'code' : 'NAD', 'name' : 'Namibia Dollar'}, - {'code' : 'NGN', 'name' : 'Nigeria Naira'}, - {'code' : 'NIO', 'name' : 'Nicaragua Cordoba'}, - {'code' : 'NOK', 'name' : 'Norway Krone'}, - {'code' : 'NPR', 'name' : 'Nepal Rupee'}, - {'code' : 'NZD', 'name' : 'New Zealand Dollar'}, - {'code' : 'OMR', 'name' : 'Oman Rial'}, - {'code' : 'PAB', 'name' : 'Panama Balboa'}, - {'code' : 'PEN', 'name' : 'Peru Nuevo Sol'}, - {'code' : 'PGK', 'name' : 'Papua New Guinea Kina'}, - {'code' : 'PHP', 'name' : 'Philippines Peso'}, - {'code' : 'PKR', 'name' : 'Pakistan Rupee'}, - {'code' : 'PLN', 'name' : 'Poland Zloty'}, - {'code' : 'PYG', 'name' : 'Paraguay Guarani'}, - {'code' : 'QAR', 'name' : 'Qatar Riyal'}, - {'code' : 'RON', 'name' : 'Romania New Leu'}, - {'code' : 'RSD', 'name' : 'Serbia Dinar'}, - {'code' : 'RUB', 'name' : 'Russia Ruble'}, - {'code' : 'RWF', 'name' : 'Rwanda Franc'}, - {'code' : 'SAR', 'name' : 'Saudi Arabia Riyal'}, - {'code' : 'SBD', 'name' : 'Solomon Islands Dollar'}, - {'code' : 'SCR', 'name' : 'Seychelles Rupee'}, - {'code' : 'SDG', 'name' : 'Sudan Pound'}, - {'code' : 'SEK', 'name' : 'Sweden Krona'}, - {'code' : 'SGD', 'name' : 'Singapore Dollar'}, - {'code' : 'SHP', 'name' : 'Saint Helena Pound'}, - {'code' : 'SLL', 'name' : 'Sierra Leone Leone'}, - {'code' : 'SOS', 'name' : 'Somalia Shilling'}, - {'code' : 'SPL', 'name' : 'Seborga Luigino'}, - {'code' : 'SRD', 'name' : 'Suriname Dollar'}, - {'code' : 'STD', 'name' : 'São Tomé and Príncipe Dobra'}, - {'code' : 'SVC', 'name' : 'El Salvador Colon'}, - {'code' : 'SYP', 'name' : 'Syria Pound'}, - {'code' : 'SZL', 'name' : 'Swaziland Lilangeni'}, - {'code' : 'THB', 'name' : 'Thailand Baht'}, - {'code' : 'TJS', 'name' : 'Tajikistan Somoni'}, - {'code' : 'TMT', 'name' : 'Turkmenistan Manat'}, - {'code' : 'TND', 'name' : 'Tunisia Dinar'}, - {'code' : 'TOP', 'name' : 'Tonga Pa\'anga'}, - {'code' : 'TRY', 'name' : 'Turkey Lira'}, - {'code' : 'TTD', 'name' : 'Trinidad and Tobago Dollar'}, - {'code' : 'TVD', 'name' : 'Tuvalu Dollar'}, - {'code' : 'TWD', 'name' : 'Taiwan New Dollar'}, - {'code' : 'TZS', 'name' : 'Tanzania Shilling'}, - {'code' : 'UAH', 'name' : 'Ukraine Hryvnia'}, - {'code' : 'UGX', 'name' : 'Uganda Shilling'}, - {'code' : 'USD', 'name' : 'United States Dollar'}, - {'code' : 'UYU', 'name' : 'Uruguay Peso'}, - {'code' : 'UZS', 'name' : 'Uzbekistan Som'}, - {'code' : 'VEF', 'name' : 'Venezuela Bolivar'}, - {'code' : 'VND', 'name' : 'Viet Nam Dong'}, - {'code' : 'VUV', 'name' : 'Vanuatu Vatu'}, - {'code' : 'WST', 'name' : 'Samoa Tala'}, - {'code' : 'XAF', 'name' : 'Communauté Financière Africaine (BEAC) CFA Franc BEAC'}, - {'code' : 'XCD', 'name' : 'East Caribbean Dollar'}, - {'code' : 'XDR', 'name' : 'International Monetary Fund (IMF) Special Drawing Rights'}, - {'code' : 'XOF', 'name' : 'Communauté Financière Africaine (BCEAO) Franc'}, - {'code' : 'XPF', 'name' : 'Comptoirs Français du Pacifique (CFP) Franc'}, - {'code' : 'YER', 'name' : 'Yemen Rial'}, - {'code' : 'ZAR', 'name' : 'South Africa Rand'}, - {'code' : 'ZMW', 'name' : 'Zambia Kwacha'}, - {'code' : 'ZWD', 'name' : 'Zimbabwe Dollar'} - ], - - // return the names of all valide colors - colorNames : [ "AliceBlue", "Black", "Navy", "DarkBlue", "MediumBlue", "Blue", "DarkGreen", "Green", "Teal", "DarkCyan", "DeepSkyBlue", "DarkTurquoise", "MediumSpringGreen", "Lime", "SpringGreen", - "Aqua", "Cyan", "MidnightBlue", "DodgerBlue", "LightSeaGreen", "ForestGreen", "SeaGreen", "DarkSlateGray", "LimeGreen", "MediumSeaGreen", "Turquoise", "RoyalBlue", "SteelBlue", "DarkSlateBlue", "MediumTurquoise", - "Indigo", "DarkOliveGreen", "CadetBlue", "CornflowerBlue", "RebeccaPurple", "MediumAquaMarine", "DimGray", "SlateBlue", "OliveDrab", "SlateGray", "LightSlateGray", "MediumSlateBlue", "LawnGreen", "Chartreuse", - "Aquamarine", "Maroon", "Purple", "Olive", "Gray", "SkyBlue", "LightSkyBlue", "BlueViolet", "DarkRed", "DarkMagenta", "SaddleBrown", "Ivory", "White", - "DarkSeaGreen", "LightGreen", "MediumPurple", "DarkViolet", "PaleGreen", "DarkOrchid", "YellowGreen", "Sienna", "Brown", "DarkGray", "LightBlue", "GreenYellow", "PaleTurquoise", "LightSteelBlue", "PowderBlue", - "FireBrick", "DarkGoldenRod", "MediumOrchid", "RosyBrown", "DarkKhaki", "Silver", "MediumVioletRed", "IndianRed", "Peru", "Chocolate", "Tan", "LightGray", "Thistle", "Orchid", "GoldenRod", "PaleVioletRed", - "Crimson", "Gainsboro", "Plum", "BurlyWood", "LightCyan", "Lavender", "DarkSalmon", "Violet", "PaleGoldenRod", "LightCoral", "Khaki", "AliceBlue", "HoneyDew", "Azure", "SandyBrown", "Wheat", "Beige", "WhiteSmoke", - "MintCream", "GhostWhite", "Salmon", "AntiqueWhite", "Linen", "LightGoldenRodYellow", "OldLace", "Red", "Fuchsia", "Magenta", "DeepPink", "OrangeRed", "Tomato", "HotPink", "Coral", "DarkOrange", "LightSalmon", "Orange", - "LightPink", "Pink", "Gold", "PeachPuff", "NavajoWhite", "Moccasin", "Bisque", "MistyRose", "BlanchedAlmond", "PapayaWhip", "LavenderBlush", "SeaShell", "Cornsilk", "LemonChiffon", "FloralWhite", "Snow", "Yellow", "LightYellow" - ], - - // Data taken from https://www.sec.gov/rules/other/4-460list.htm - company: [ "3Com Corp", - "3M Company", - "A.G. Edwards Inc.", - "Abbott Laboratories", - "Abercrombie & Fitch Co.", - "ABM Industries Incorporated", - "Ace Hardware Corporation", - "ACT Manufacturing Inc.", - "Acterna Corp.", - "Adams Resources & Energy, Inc.", - "ADC Telecommunications, Inc.", - "Adelphia Communications Corporation", - "Administaff, Inc.", - "Adobe Systems Incorporated", - "Adolph Coors Company", - "Advance Auto Parts, Inc.", - "Advanced Micro Devices, Inc.", - "AdvancePCS, Inc.", - "Advantica Restaurant Group, Inc.", - "The AES Corporation", - "Aetna Inc.", - "Affiliated Computer Services, Inc.", - "AFLAC Incorporated", - "AGCO Corporation", - "Agilent Technologies, Inc.", - "Agway Inc.", - "Apartment Investment and Management Company", - "Air Products and Chemicals, Inc.", - "Airborne, Inc.", - "Airgas, Inc.", - "AK Steel Holding Corporation", - "Alaska Air Group, Inc.", - "Alberto-Culver Company", - "Albertson's, Inc.", - "Alcoa Inc.", - "Alleghany Corporation", - "Allegheny Energy, Inc.", - "Allegheny Technologies Incorporated", - "Allergan, Inc.", - "ALLETE, Inc.", - "Alliant Energy Corporation", - "Allied Waste Industries, Inc.", - "Allmerica Financial Corporation", - "The Allstate Corporation", - "ALLTEL Corporation", - "The Alpine Group, Inc.", - "Amazon.com, Inc.", - "AMC Entertainment Inc.", - "American Power Conversion Corporation", - "Amerada Hess Corporation", - "AMERCO", - "Ameren Corporation", - "America West Holdings Corporation", - "American Axle & Manufacturing Holdings, Inc.", - "American Eagle Outfitters, Inc.", - "American Electric Power Company, Inc.", - "American Express Company", - "American Financial Group, Inc.", - "American Greetings Corporation", - "American International Group, Inc.", - "American Standard Companies Inc.", - "American Water Works Company, Inc.", - "AmerisourceBergen Corporation", - "Ames Department Stores, Inc.", - "Amgen Inc.", - "Amkor Technology, Inc.", - "AMR Corporation", - "AmSouth Bancorp.", - "Amtran, Inc.", - "Anadarko Petroleum Corporation", - "Analog Devices, Inc.", - "Anheuser-Busch Companies, Inc.", - "Anixter International Inc.", - "AnnTaylor Inc.", - "Anthem, Inc.", - "AOL Time Warner Inc.", - "Aon Corporation", - "Apache Corporation", - "Apple Computer, Inc.", - "Applera Corporation", - "Applied Industrial Technologies, Inc.", - "Applied Materials, Inc.", - "Aquila, Inc.", - "ARAMARK Corporation", - "Arch Coal, Inc.", - "Archer Daniels Midland Company", - "Arkansas Best Corporation", - "Armstrong Holdings, Inc.", - "Arrow Electronics, Inc.", - "ArvinMeritor, Inc.", - "Ashland Inc.", - "Astoria Financial Corporation", - "AT&T Corp.", - "Atmel Corporation", - "Atmos Energy Corporation", - "Audiovox Corporation", - "Autoliv, Inc.", - "Automatic Data Processing, Inc.", - "AutoNation, Inc.", - "AutoZone, Inc.", - "Avaya Inc.", - "Avery Dennison Corporation", - "Avista Corporation", - "Avnet, Inc.", - "Avon Products, Inc.", - "Baker Hughes Incorporated", - "Ball Corporation", - "Bank of America Corporation", - "The Bank of New York Company, Inc.", - "Bank One Corporation", - "Banknorth Group, Inc.", - "Banta Corporation", - "Barnes & Noble, Inc.", - "Bausch & Lomb Incorporated", - "Baxter International Inc.", - "BB&T Corporation", - "The Bear Stearns Companies Inc.", - "Beazer Homes USA, Inc.", - "Beckman Coulter, Inc.", - "Becton, Dickinson and Company", - "Bed Bath & Beyond Inc.", - "Belk, Inc.", - "Bell Microproducts Inc.", - "BellSouth Corporation", - "Belo Corp.", - "Bemis Company, Inc.", - "Benchmark Electronics, Inc.", - "Berkshire Hathaway Inc.", - "Best Buy Co., Inc.", - "Bethlehem Steel Corporation", - "Beverly Enterprises, Inc.", - "Big Lots, Inc.", - "BJ Services Company", - "BJ's Wholesale Club, Inc.", - "The Black & Decker Corporation", - "Black Hills Corporation", - "BMC Software, Inc.", - "The Boeing Company", - "Boise Cascade Corporation", - "Borders Group, Inc.", - "BorgWarner Inc.", - "Boston Scientific Corporation", - "Bowater Incorporated", - "Briggs & Stratton Corporation", - "Brightpoint, Inc.", - "Brinker International, Inc.", - "Bristol-Myers Squibb Company", - "Broadwing, Inc.", - "Brown Shoe Company, Inc.", - "Brown-Forman Corporation", - "Brunswick Corporation", - "Budget Group, Inc.", - "Burlington Coat Factory Warehouse Corporation", - "Burlington Industries, Inc.", - "Burlington Northern Santa Fe Corporation", - "Burlington Resources Inc.", - "C. H. Robinson Worldwide Inc.", - "Cablevision Systems Corp", - "Cabot Corp", - "Cadence Design Systems, Inc.", - "Calpine Corp.", - "Campbell Soup Co.", - "Capital One Financial Corp.", - "Cardinal Health Inc.", - "Caremark Rx Inc.", - "Carlisle Cos. Inc.", - "Carpenter Technology Corp.", - "Casey's General Stores Inc.", - "Caterpillar Inc.", - "CBRL Group Inc.", - "CDI Corp.", - "CDW Computer Centers Inc.", - "CellStar Corp.", - "Cendant Corp", - "Cenex Harvest States Cooperatives", - "Centex Corp.", - "CenturyTel Inc.", - "Ceridian Corp.", - "CH2M Hill Cos. Ltd.", - "Champion Enterprises Inc.", - "Charles Schwab Corp.", - "Charming Shoppes Inc.", - "Charter Communications Inc.", - "Charter One Financial Inc.", - "ChevronTexaco Corp.", - "Chiquita Brands International Inc.", - "Chubb Corp", - "Ciena Corp.", - "Cigna Corp", - "Cincinnati Financial Corp.", - "Cinergy Corp.", - "Cintas Corp.", - "Circuit City Stores Inc.", - "Cisco Systems Inc.", - "Citigroup, Inc", - "Citizens Communications Co.", - "CKE Restaurants Inc.", - "Clear Channel Communications Inc.", - "The Clorox Co.", - "CMGI Inc.", - "CMS Energy Corp.", - "CNF Inc.", - "Coca-Cola Co.", - "Coca-Cola Enterprises Inc.", - "Colgate-Palmolive Co.", - "Collins & Aikman Corp.", - "Comcast Corp.", - "Comdisco Inc.", - "Comerica Inc.", - "Comfort Systems USA Inc.", - "Commercial Metals Co.", - "Community Health Systems Inc.", - "Compass Bancshares Inc", - "Computer Associates International Inc.", - "Computer Sciences Corp.", - "Compuware Corp.", - "Comverse Technology Inc.", - "ConAgra Foods Inc.", - "Concord EFS Inc.", - "Conectiv, Inc", - "Conoco Inc", - "Conseco Inc.", - "Consolidated Freightways Corp.", - "Consolidated Edison Inc.", - "Constellation Brands Inc.", - "Constellation Emergy Group Inc.", - "Continental Airlines Inc.", - "Convergys Corp.", - "Cooper Cameron Corp.", - "Cooper Industries Ltd.", - "Cooper Tire & Rubber Co.", - "Corn Products International Inc.", - "Corning Inc.", - "Costco Wholesale Corp.", - "Countrywide Credit Industries Inc.", - "Coventry Health Care Inc.", - "Cox Communications Inc.", - "Crane Co.", - "Crompton Corp.", - "Crown Cork & Seal Co. Inc.", - "CSK Auto Corp.", - "CSX Corp.", - "Cummins Inc.", - "CVS Corp.", - "Cytec Industries Inc.", - "D&K Healthcare Resources, Inc.", - "D.R. Horton Inc.", - "Dana Corporation", - "Danaher Corporation", - "Darden Restaurants Inc.", - "DaVita Inc.", - "Dean Foods Company", - "Deere & Company", - "Del Monte Foods Co", - "Dell Computer Corporation", - "Delphi Corp.", - "Delta Air Lines Inc.", - "Deluxe Corporation", - "Devon Energy Corporation", - "Di Giorgio Corporation", - "Dial Corporation", - "Diebold Incorporated", - "Dillard's Inc.", - "DIMON Incorporated", - "Dole Food Company, Inc.", - "Dollar General Corporation", - "Dollar Tree Stores, Inc.", - "Dominion Resources, Inc.", - "Domino's Pizza LLC", - "Dover Corporation, Inc.", - "Dow Chemical Company", - "Dow Jones & Company, Inc.", - "DPL Inc.", - "DQE Inc.", - "Dreyer's Grand Ice Cream, Inc.", - "DST Systems, Inc.", - "DTE Energy Co.", - "E.I. Du Pont de Nemours and Company", - "Duke Energy Corp", - "Dun & Bradstreet Inc.", - "DURA Automotive Systems Inc.", - "DynCorp", - "Dynegy Inc.", - "E*Trade Group, Inc.", - "E.W. Scripps Company", - "Earthlink, Inc.", - "Eastman Chemical Company", - "Eastman Kodak Company", - "Eaton Corporation", - "Echostar Communications Corporation", - "Ecolab Inc.", - "Edison International", - "EGL Inc.", - "El Paso Corporation", - "Electronic Arts Inc.", - "Electronic Data Systems Corp.", - "Eli Lilly and Company", - "EMC Corporation", - "Emcor Group Inc.", - "Emerson Electric Co.", - "Encompass Services Corporation", - "Energizer Holdings Inc.", - "Energy East Corporation", - "Engelhard Corporation", - "Enron Corp.", - "Entergy Corporation", - "Enterprise Products Partners L.P.", - "EOG Resources, Inc.", - "Equifax Inc.", - "Equitable Resources Inc.", - "Equity Office Properties Trust", - "Equity Residential Properties Trust", - "Estee Lauder Companies Inc.", - "Exelon Corporation", - "Exide Technologies", - "Expeditors International of Washington Inc.", - "Express Scripts Inc.", - "ExxonMobil Corporation", - "Fairchild Semiconductor International Inc.", - "Family Dollar Stores Inc.", - "Farmland Industries Inc.", - "Federal Mogul Corp.", - "Federated Department Stores Inc.", - "Federal Express Corp.", - "Felcor Lodging Trust Inc.", - "Ferro Corp.", - "Fidelity National Financial Inc.", - "Fifth Third Bancorp", - "First American Financial Corp.", - "First Data Corp.", - "First National of Nebraska Inc.", - "First Tennessee National Corp.", - "FirstEnergy Corp.", - "Fiserv Inc.", - "Fisher Scientific International Inc.", - "FleetBoston Financial Co.", - "Fleetwood Enterprises Inc.", - "Fleming Companies Inc.", - "Flowers Foods Inc.", - "Flowserv Corp", - "Fluor Corp", - "FMC Corp", - "Foamex International Inc", - "Foot Locker Inc", - "Footstar Inc.", - "Ford Motor Co", - "Forest Laboratories Inc.", - "Fortune Brands Inc.", - "Foster Wheeler Ltd.", - "FPL Group Inc.", - "Franklin Resources Inc.", - "Freeport McMoran Copper & Gold Inc.", - "Frontier Oil Corp", - "Furniture Brands International Inc.", - "Gannett Co., Inc.", - "Gap Inc.", - "Gateway Inc.", - "GATX Corporation", - "Gemstar-TV Guide International Inc.", - "GenCorp Inc.", - "General Cable Corporation", - "General Dynamics Corporation", - "General Electric Company", - "General Mills Inc", - "General Motors Corporation", - "Genesis Health Ventures Inc.", - "Gentek Inc.", - "Gentiva Health Services Inc.", - "Genuine Parts Company", - "Genuity Inc.", - "Genzyme Corporation", - "Georgia Gulf Corporation", - "Georgia-Pacific Corporation", - "Gillette Company", - "Gold Kist Inc.", - "Golden State Bancorp Inc.", - "Golden West Financial Corporation", - "Goldman Sachs Group Inc.", - "Goodrich Corporation", - "The Goodyear Tire & Rubber Company", - "Granite Construction Incorporated", - "Graybar Electric Company Inc.", - "Great Lakes Chemical Corporation", - "Great Plains Energy Inc.", - "GreenPoint Financial Corp.", - "Greif Bros. Corporation", - "Grey Global Group Inc.", - "Group 1 Automotive Inc.", - "Guidant Corporation", - "H&R Block Inc.", - "H.B. Fuller Company", - "H.J. Heinz Company", - "Halliburton Co.", - "Harley-Davidson Inc.", - "Harman International Industries Inc.", - "Harrah's Entertainment Inc.", - "Harris Corp.", - "Harsco Corp.", - "Hartford Financial Services Group Inc.", - "Hasbro Inc.", - "Hawaiian Electric Industries Inc.", - "HCA Inc.", - "Health Management Associates Inc.", - "Health Net Inc.", - "Healthsouth Corp", - "Henry Schein Inc.", - "Hercules Inc.", - "Herman Miller Inc.", - "Hershey Foods Corp.", - "Hewlett-Packard Company", - "Hibernia Corp.", - "Hillenbrand Industries Inc.", - "Hilton Hotels Corp.", - "Hollywood Entertainment Corp.", - "Home Depot Inc.", - "Hon Industries Inc.", - "Honeywell International Inc.", - "Hormel Foods Corp.", - "Host Marriott Corp.", - "Household International Corp.", - "Hovnanian Enterprises Inc.", - "Hub Group Inc.", - "Hubbell Inc.", - "Hughes Supply Inc.", - "Humana Inc.", - "Huntington Bancshares Inc.", - "Idacorp Inc.", - "IDT Corporation", - "IKON Office Solutions Inc.", - "Illinois Tool Works Inc.", - "IMC Global Inc.", - "Imperial Sugar Company", - "IMS Health Inc.", - "Ingles Market Inc", - "Ingram Micro Inc.", - "Insight Enterprises Inc.", - "Integrated Electrical Services Inc.", - "Intel Corporation", - "International Paper Co.", - "Interpublic Group of Companies Inc.", - "Interstate Bakeries Corporation", - "International Business Machines Corp.", - "International Flavors & Fragrances Inc.", - "International Multifoods Corporation", - "Intuit Inc.", - "IT Group Inc.", - "ITT Industries Inc.", - "Ivax Corp.", - "J.B. Hunt Transport Services Inc.", - "J.C. Penny Co.", - "J.P. Morgan Chase & Co.", - "Jabil Circuit Inc.", - "Jack In The Box Inc.", - "Jacobs Engineering Group Inc.", - "JDS Uniphase Corp.", - "Jefferson-Pilot Co.", - "John Hancock Financial Services Inc.", - "Johnson & Johnson", - "Johnson Controls Inc.", - "Jones Apparel Group Inc.", - "KB Home", - "Kellogg Company", - "Kellwood Company", - "Kelly Services Inc.", - "Kemet Corp.", - "Kennametal Inc.", - "Kerr-McGee Corporation", - "KeyCorp", - "KeySpan Corp.", - "Kimball International Inc.", - "Kimberly-Clark Corporation", - "Kindred Healthcare Inc.", - "KLA-Tencor Corporation", - "K-Mart Corp.", - "Knight-Ridder Inc.", - "Kohl's Corp.", - "KPMG Consulting Inc.", - "Kroger Co.", - "L-3 Communications Holdings Inc.", - "Laboratory Corporation of America Holdings", - "Lam Research Corporation", - "LandAmerica Financial Group Inc.", - "Lands' End Inc.", - "Landstar System Inc.", - "La-Z-Boy Inc.", - "Lear Corporation", - "Legg Mason Inc.", - "Leggett & Platt Inc.", - "Lehman Brothers Holdings Inc.", - "Lennar Corporation", - "Lennox International Inc.", - "Level 3 Communications Inc.", - "Levi Strauss & Co.", - "Lexmark International Inc.", - "Limited Inc.", - "Lincoln National Corporation", - "Linens 'n Things Inc.", - "Lithia Motors Inc.", - "Liz Claiborne Inc.", - "Lockheed Martin Corporation", - "Loews Corporation", - "Longs Drug Stores Corporation", - "Louisiana-Pacific Corporation", - "Lowe's Companies Inc.", - "LSI Logic Corporation", - "The LTV Corporation", - "The Lubrizol Corporation", - "Lucent Technologies Inc.", - "Lyondell Chemical Company", - "M & T Bank Corporation", - "Magellan Health Services Inc.", - "Mail-Well Inc.", - "Mandalay Resort Group", - "Manor Care Inc.", - "Manpower Inc.", - "Marathon Oil Corporation", - "Mariner Health Care Inc.", - "Markel Corporation", - "Marriott International Inc.", - "Marsh & McLennan Companies Inc.", - "Marsh Supermarkets Inc.", - "Marshall & Ilsley Corporation", - "Martin Marietta Materials Inc.", - "Masco Corporation", - "Massey Energy Company", - "MasTec Inc.", - "Mattel Inc.", - "Maxim Integrated Products Inc.", - "Maxtor Corporation", - "Maxxam Inc.", - "The May Department Stores Company", - "Maytag Corporation", - "MBNA Corporation", - "McCormick & Company Incorporated", - "McDonald's Corporation", - "The McGraw-Hill Companies Inc.", - "McKesson Corporation", - "McLeodUSA Incorporated", - "M.D.C. Holdings Inc.", - "MDU Resources Group Inc.", - "MeadWestvaco Corporation", - "Medtronic Inc.", - "Mellon Financial Corporation", - "The Men's Wearhouse Inc.", - "Merck & Co., Inc.", - "Mercury General Corporation", - "Merrill Lynch & Co. Inc.", - "Metaldyne Corporation", - "Metals USA Inc.", - "MetLife Inc.", - "Metris Companies Inc", - "MGIC Investment Corporation", - "MGM Mirage", - "Michaels Stores Inc.", - "Micron Technology Inc.", - "Microsoft Corporation", - "Milacron Inc.", - "Millennium Chemicals Inc.", - "Mirant Corporation", - "Mohawk Industries Inc.", - "Molex Incorporated", - "The MONY Group Inc.", - "Morgan Stanley Dean Witter & Co.", - "Motorola Inc.", - "MPS Group Inc.", - "Murphy Oil Corporation", - "Nabors Industries Inc", - "Nacco Industries Inc", - "Nash Finch Company", - "National City Corp.", - "National Commerce Financial Corporation", - "National Fuel Gas Company", - "National Oilwell Inc", - "National Rural Utilities Cooperative Finance Corporation", - "National Semiconductor Corporation", - "National Service Industries Inc", - "Navistar International Corporation", - "NCR Corporation", - "The Neiman Marcus Group Inc.", - "New Jersey Resources Corporation", - "New York Times Company", - "Newell Rubbermaid Inc", - "Newmont Mining Corporation", - "Nextel Communications Inc", - "Nicor Inc", - "Nike Inc", - "NiSource Inc", - "Noble Energy Inc", - "Nordstrom Inc", - "Norfolk Southern Corporation", - "Nortek Inc", - "North Fork Bancorporation Inc", - "Northeast Utilities System", - "Northern Trust Corporation", - "Northrop Grumman Corporation", - "NorthWestern Corporation", - "Novellus Systems Inc", - "NSTAR", - "NTL Incorporated", - "Nucor Corp", - "Nvidia Corp", - "NVR Inc", - "Northwest Airlines Corp", - "Occidental Petroleum Corp", - "Ocean Energy Inc", - "Office Depot Inc.", - "OfficeMax Inc", - "OGE Energy Corp", - "Oglethorpe Power Corp.", - "Ohio Casualty Corp.", - "Old Republic International Corp.", - "Olin Corp.", - "OM Group Inc", - "Omnicare Inc", - "Omnicom Group", - "On Semiconductor Corp", - "ONEOK Inc", - "Oracle Corp", - "Oshkosh Truck Corp", - "Outback Steakhouse Inc.", - "Owens & Minor Inc.", - "Owens Corning", - "Owens-Illinois Inc", - "Oxford Health Plans Inc", - "Paccar Inc", - "PacifiCare Health Systems Inc", - "Packaging Corp. of America", - "Pactiv Corp", - "Pall Corp", - "Pantry Inc", - "Park Place Entertainment Corp", - "Parker Hannifin Corp.", - "Pathmark Stores Inc.", - "Paychex Inc", - "Payless Shoesource Inc", - "Penn Traffic Co.", - "Pennzoil-Quaker State Company", - "Pentair Inc", - "Peoples Energy Corp.", - "PeopleSoft Inc", - "Pep Boys Manny, Moe & Jack", - "Potomac Electric Power Co.", - "Pepsi Bottling Group Inc.", - "PepsiAmericas Inc.", - "PepsiCo Inc.", - "Performance Food Group Co.", - "Perini Corp", - "PerkinElmer Inc", - "Perot Systems Corp", - "Petco Animal Supplies Inc.", - "Peter Kiewit Sons', Inc.", - "PETsMART Inc", - "Pfizer Inc", - "Pacific Gas & Electric Corp.", - "Pharmacia Corp", - "Phar Mor Inc.", - "Phelps Dodge Corp.", - "Philip Morris Companies Inc.", - "Phillips Petroleum Co", - "Phillips Van Heusen Corp.", - "Phoenix Companies Inc", - "Pier 1 Imports Inc.", - "Pilgrim's Pride Corporation", - "Pinnacle West Capital Corp", - "Pioneer-Standard Electronics Inc.", - "Pitney Bowes Inc.", - "Pittston Brinks Group", - "Plains All American Pipeline LP", - "PNC Financial Services Group Inc.", - "PNM Resources Inc", - "Polaris Industries Inc.", - "Polo Ralph Lauren Corp", - "PolyOne Corp", - "Popular Inc", - "Potlatch Corp", - "PPG Industries Inc", - "PPL Corp", - "Praxair Inc", - "Precision Castparts Corp", - "Premcor Inc.", - "Pride International Inc", - "Primedia Inc", - "Principal Financial Group Inc.", - "Procter & Gamble Co.", - "Pro-Fac Cooperative Inc.", - "Progress Energy Inc", - "Progressive Corporation", - "Protective Life Corp", - "Provident Financial Group", - "Providian Financial Corp.", - "Prudential Financial Inc.", - "PSS World Medical Inc", - "Public Service Enterprise Group Inc.", - "Publix Super Markets Inc.", - "Puget Energy Inc.", - "Pulte Homes Inc", - "Qualcomm Inc", - "Quanta Services Inc.", - "Quantum Corp", - "Quest Diagnostics Inc.", - "Questar Corp", - "Quintiles Transnational", - "Qwest Communications Intl Inc", - "R.J. Reynolds Tobacco Company", - "R.R. Donnelley & Sons Company", - "Radio Shack Corporation", - "Raymond James Financial Inc.", - "Raytheon Company", - "Reader's Digest Association Inc.", - "Reebok International Ltd.", - "Regions Financial Corp.", - "Regis Corporation", - "Reliance Steel & Aluminum Co.", - "Reliant Energy Inc.", - "Rent A Center Inc", - "Republic Services Inc", - "Revlon Inc", - "RGS Energy Group Inc", - "Rite Aid Corp", - "Riverwood Holding Inc.", - "RoadwayCorp", - "Robert Half International Inc.", - "Rock-Tenn Co", - "Rockwell Automation Inc", - "Rockwell Collins Inc", - "Rohm & Haas Co.", - "Ross Stores Inc", - "RPM Inc.", - "Ruddick Corp", - "Ryder System Inc", - "Ryerson Tull Inc", - "Ryland Group Inc.", - "Sabre Holdings Corp", - "Safeco Corp", - "Safeguard Scientifics Inc.", - "Safeway Inc", - "Saks Inc", - "Sanmina-SCI Inc", - "Sara Lee Corp", - "SBC Communications Inc", - "Scana Corp.", - "Schering-Plough Corp", - "Scholastic Corp", - "SCI Systems Onc.", - "Science Applications Intl. Inc.", - "Scientific-Atlanta Inc", - "Scotts Company", - "Seaboard Corp", - "Sealed Air Corp", - "Sears Roebuck & Co", - "Sempra Energy", - "Sequa Corp", - "Service Corp. International", - "ServiceMaster Co", - "Shaw Group Inc", - "Sherwin-Williams Company", - "Shopko Stores Inc", - "Siebel Systems Inc", - "Sierra Health Services Inc", - "Sierra Pacific Resources", - "Silgan Holdings Inc.", - "Silicon Graphics Inc", - "Simon Property Group Inc", - "SLM Corporation", - "Smith International Inc", - "Smithfield Foods Inc", - "Smurfit-Stone Container Corp", - "Snap-On Inc", - "Solectron Corp", - "Solutia Inc", - "Sonic Automotive Inc.", - "Sonoco Products Co.", - "Southern Company", - "Southern Union Company", - "SouthTrust Corp.", - "Southwest Airlines Co", - "Southwest Gas Corp", - "Sovereign Bancorp Inc.", - "Spartan Stores Inc", - "Spherion Corp", - "Sports Authority Inc", - "Sprint Corp.", - "SPX Corp", - "St. Jude Medical Inc", - "St. Paul Cos.", - "Staff Leasing Inc.", - "StanCorp Financial Group Inc", - "Standard Pacific Corp.", - "Stanley Works", - "Staples Inc", - "Starbucks Corp", - "Starwood Hotels & Resorts Worldwide Inc", - "State Street Corp.", - "Stater Bros. Holdings Inc.", - "Steelcase Inc", - "Stein Mart Inc", - "Stewart & Stevenson Services Inc", - "Stewart Information Services Corp", - "Stilwell Financial Inc", - "Storage Technology Corporation", - "Stryker Corp", - "Sun Healthcare Group Inc.", - "Sun Microsystems Inc.", - "SunGard Data Systems Inc.", - "Sunoco Inc.", - "SunTrust Banks Inc", - "Supervalu Inc", - "Swift Transportation, Co., Inc", - "Symbol Technologies Inc", - "Synovus Financial Corp.", - "Sysco Corp", - "Systemax Inc.", - "Target Corp.", - "Tech Data Corporation", - "TECO Energy Inc", - "Tecumseh Products Company", - "Tektronix Inc", - "Teleflex Incorporated", - "Telephone & Data Systems Inc", - "Tellabs Inc.", - "Temple-Inland Inc", - "Tenet Healthcare Corporation", - "Tenneco Automotive Inc.", - "Teradyne Inc", - "Terex Corp", - "Tesoro Petroleum Corp.", - "Texas Industries Inc.", - "Texas Instruments Incorporated", - "Textron Inc", - "Thermo Electron Corporation", - "Thomas & Betts Corporation", - "Tiffany & Co", - "Timken Company", - "TJX Companies Inc", - "TMP Worldwide Inc", - "Toll Brothers Inc", - "Torchmark Corporation", - "Toro Company", - "Tower Automotive Inc.", - "Toys 'R' Us Inc", - "Trans World Entertainment Corp.", - "TransMontaigne Inc", - "Transocean Inc", - "TravelCenters of America Inc.", - "Triad Hospitals Inc", - "Tribune Company", - "Trigon Healthcare Inc.", - "Trinity Industries Inc", - "Trump Hotels & Casino Resorts Inc.", - "TruServ Corporation", - "TRW Inc", - "TXU Corp", - "Tyson Foods Inc", - "U.S. Bancorp", - "U.S. Industries Inc.", - "UAL Corporation", - "UGI Corporation", - "Unified Western Grocers Inc", - "Union Pacific Corporation", - "Union Planters Corp", - "Unisource Energy Corp", - "Unisys Corporation", - "United Auto Group Inc", - "United Defense Industries Inc.", - "United Parcel Service Inc", - "United Rentals Inc", - "United Stationers Inc", - "United Technologies Corporation", - "UnitedHealth Group Incorporated", - "Unitrin Inc", - "Universal Corporation", - "Universal Forest Products Inc", - "Universal Health Services Inc", - "Unocal Corporation", - "Unova Inc", - "UnumProvident Corporation", - "URS Corporation", - "US Airways Group Inc", - "US Oncology Inc", - "USA Interactive", - "USFreighways Corporation", - "USG Corporation", - "UST Inc", - "Valero Energy Corporation", - "Valspar Corporation", - "Value City Department Stores Inc", - "Varco International Inc", - "Vectren Corporation", - "Veritas Software Corporation", - "Verizon Communications Inc", - "VF Corporation", - "Viacom Inc", - "Viad Corp", - "Viasystems Group Inc", - "Vishay Intertechnology Inc", - "Visteon Corporation", - "Volt Information Sciences Inc", - "Vulcan Materials Company", - "W.R. Berkley Corporation", - "W.R. Grace & Co", - "W.W. Grainger Inc", - "Wachovia Corporation", - "Wakenhut Corporation", - "Walgreen Co", - "Wallace Computer Services Inc", - "Wal-Mart Stores Inc", - "Walt Disney Co", - "Walter Industries Inc", - "Washington Mutual Inc", - "Washington Post Co.", - "Waste Management Inc", - "Watsco Inc", - "Weatherford International Inc", - "Weis Markets Inc.", - "Wellpoint Health Networks Inc", - "Wells Fargo & Company", - "Wendy's International Inc", - "Werner Enterprises Inc", - "WESCO International Inc", - "Western Digital Inc", - "Western Gas Resources Inc", - "WestPoint Stevens Inc", - "Weyerhauser Company", - "WGL Holdings Inc", - "Whirlpool Corporation", - "Whole Foods Market Inc", - "Willamette Industries Inc.", - "Williams Companies Inc", - "Williams Sonoma Inc", - "Winn Dixie Stores Inc", - "Wisconsin Energy Corporation", - "Wm Wrigley Jr Company", - "World Fuel Services Corporation", - "WorldCom Inc", - "Worthington Industries Inc", - "WPS Resources Corporation", - "Wyeth", - "Wyndham International Inc", - "Xcel Energy Inc", - "Xerox Corp", - "Xilinx Inc", - "XO Communications Inc", - "Yellow Corporation", - "York International Corp", - "Yum Brands Inc.", - "Zale Corporation", - "Zions Bancorporation" - ], - - fileExtension : { - "raster" : ["bmp", "gif", "gpl", "ico", "jpeg", "psd", "png", "psp", "raw", "tiff"], - "vector" : ["3dv", "amf", "awg", "ai", "cgm", "cdr", "cmx", "dxf", "e2d", "egt", "eps", "fs", "odg", "svg", "xar"], - "3d" : ["3dmf", "3dm", "3mf", "3ds", "an8", "aoi", "blend", "cal3d", "cob", "ctm", "iob", "jas", "max", "mb", "mdx", "obj", "x", "x3d"], - "document" : ["doc", "docx", "dot", "html", "xml", "odt", "odm", "ott", "csv", "rtf", "tex", "xhtml", "xps"] - }, - - // Data taken from https://github.com/dmfilipenko/timezones.json/blob/master/timezones.json - timezones: [ - { - "name": "Dateline Standard Time", - "abbr": "DST", - "offset": -12, - "isdst": false, - "text": "(UTC-12:00) International Date Line West", - "utc": [ - "Etc/GMT+12" - ] - }, - { - "name": "UTC-11", - "abbr": "U", - "offset": -11, - "isdst": false, - "text": "(UTC-11:00) Coordinated Universal Time-11", - "utc": [ - "Etc/GMT+11", - "Pacific/Midway", - "Pacific/Niue", - "Pacific/Pago_Pago" - ] - }, - { - "name": "Hawaiian Standard Time", - "abbr": "HST", - "offset": -10, - "isdst": false, - "text": "(UTC-10:00) Hawaii", - "utc": [ - "Etc/GMT+10", - "Pacific/Honolulu", - "Pacific/Johnston", - "Pacific/Rarotonga", - "Pacific/Tahiti" - ] - }, - { - "name": "Alaskan Standard Time", - "abbr": "AKDT", - "offset": -8, - "isdst": true, - "text": "(UTC-09:00) Alaska", - "utc": [ - "America/Anchorage", - "America/Juneau", - "America/Nome", - "America/Sitka", - "America/Yakutat" - ] - }, - { - "name": "Pacific Standard Time (Mexico)", - "abbr": "PDT", - "offset": -7, - "isdst": true, - "text": "(UTC-08:00) Baja California", - "utc": [ - "America/Santa_Isabel" - ] - }, - { - "name": "Pacific Daylight Time", - "abbr": "PDT", - "offset": -7, - "isdst": true, - "text": "(UTC-07:00) Pacific Time (US & Canada)", - "utc": [ - "America/Dawson", - "America/Los_Angeles", - "America/Tijuana", - "America/Vancouver", - "America/Whitehorse" - ] - }, - { - "name": "Pacific Standard Time", - "abbr": "PST", - "offset": -8, - "isdst": false, - "text": "(UTC-08:00) Pacific Time (US & Canada)", - "utc": [ - "America/Dawson", - "America/Los_Angeles", - "America/Tijuana", - "America/Vancouver", - "America/Whitehorse", - "PST8PDT" - ] - }, - { - "name": "US Mountain Standard Time", - "abbr": "UMST", - "offset": -7, - "isdst": false, - "text": "(UTC-07:00) Arizona", - "utc": [ - "America/Creston", - "America/Dawson_Creek", - "America/Hermosillo", - "America/Phoenix", - "Etc/GMT+7" - ] - }, - { - "name": "Mountain Standard Time (Mexico)", - "abbr": "MDT", - "offset": -6, - "isdst": true, - "text": "(UTC-07:00) Chihuahua, La Paz, Mazatlan", - "utc": [ - "America/Chihuahua", - "America/Mazatlan" - ] - }, - { - "name": "Mountain Standard Time", - "abbr": "MDT", - "offset": -6, - "isdst": true, - "text": "(UTC-07:00) Mountain Time (US & Canada)", - "utc": [ - "America/Boise", - "America/Cambridge_Bay", - "America/Denver", - "America/Edmonton", - "America/Inuvik", - "America/Ojinaga", - "America/Yellowknife", - "MST7MDT" - ] - }, - { - "name": "Central America Standard Time", - "abbr": "CAST", - "offset": -6, - "isdst": false, - "text": "(UTC-06:00) Central America", - "utc": [ - "America/Belize", - "America/Costa_Rica", - "America/El_Salvador", - "America/Guatemala", - "America/Managua", - "America/Tegucigalpa", - "Etc/GMT+6", - "Pacific/Galapagos" - ] - }, - { - "name": "Central Standard Time", - "abbr": "CDT", - "offset": -5, - "isdst": true, - "text": "(UTC-06:00) Central Time (US & Canada)", - "utc": [ - "America/Chicago", - "America/Indiana/Knox", - "America/Indiana/Tell_City", - "America/Matamoros", - "America/Menominee", - "America/North_Dakota/Beulah", - "America/North_Dakota/Center", - "America/North_Dakota/New_Salem", - "America/Rainy_River", - "America/Rankin_Inlet", - "America/Resolute", - "America/Winnipeg", - "CST6CDT" - ] - }, - { - "name": "Central Standard Time (Mexico)", - "abbr": "CDT", - "offset": -5, - "isdst": true, - "text": "(UTC-06:00) Guadalajara, Mexico City, Monterrey", - "utc": [ - "America/Bahia_Banderas", - "America/Cancun", - "America/Merida", - "America/Mexico_City", - "America/Monterrey" - ] - }, - { - "name": "Canada Central Standard Time", - "abbr": "CCST", - "offset": -6, - "isdst": false, - "text": "(UTC-06:00) Saskatchewan", - "utc": [ - "America/Regina", - "America/Swift_Current" - ] - }, - { - "name": "SA Pacific Standard Time", - "abbr": "SPST", - "offset": -5, - "isdst": false, - "text": "(UTC-05:00) Bogota, Lima, Quito", - "utc": [ - "America/Bogota", - "America/Cayman", - "America/Coral_Harbour", - "America/Eirunepe", - "America/Guayaquil", - "America/Jamaica", - "America/Lima", - "America/Panama", - "America/Rio_Branco", - "Etc/GMT+5" - ] - }, - { - "name": "Eastern Standard Time", - "abbr": "EDT", - "offset": -4, - "isdst": true, - "text": "(UTC-05:00) Eastern Time (US & Canada)", - "utc": [ - "America/Detroit", - "America/Havana", - "America/Indiana/Petersburg", - "America/Indiana/Vincennes", - "America/Indiana/Winamac", - "America/Iqaluit", - "America/Kentucky/Monticello", - "America/Louisville", - "America/Montreal", - "America/Nassau", - "America/New_York", - "America/Nipigon", - "America/Pangnirtung", - "America/Port-au-Prince", - "America/Thunder_Bay", - "America/Toronto", - "EST5EDT" - ] - }, - { - "name": "US Eastern Standard Time", - "abbr": "UEDT", - "offset": -4, - "isdst": true, - "text": "(UTC-05:00) Indiana (East)", - "utc": [ - "America/Indiana/Marengo", - "America/Indiana/Vevay", - "America/Indianapolis" - ] - }, - { - "name": "Venezuela Standard Time", - "abbr": "VST", - "offset": -4.5, - "isdst": false, - "text": "(UTC-04:30) Caracas", - "utc": [ - "America/Caracas" - ] - }, - { - "name": "Paraguay Standard Time", - "abbr": "PYT", - "offset": -4, - "isdst": false, - "text": "(UTC-04:00) Asuncion", - "utc": [ - "America/Asuncion" - ] - }, - { - "name": "Atlantic Standard Time", - "abbr": "ADT", - "offset": -3, - "isdst": true, - "text": "(UTC-04:00) Atlantic Time (Canada)", - "utc": [ - "America/Glace_Bay", - "America/Goose_Bay", - "America/Halifax", - "America/Moncton", - "America/Thule", - "Atlantic/Bermuda" - ] - }, - { - "name": "Central Brazilian Standard Time", - "abbr": "CBST", - "offset": -4, - "isdst": false, - "text": "(UTC-04:00) Cuiaba", - "utc": [ - "America/Campo_Grande", - "America/Cuiaba" - ] - }, - { - "name": "SA Western Standard Time", - "abbr": "SWST", - "offset": -4, - "isdst": false, - "text": "(UTC-04:00) Georgetown, La Paz, Manaus, San Juan", - "utc": [ - "America/Anguilla", - "America/Antigua", - "America/Aruba", - "America/Barbados", - "America/Blanc-Sablon", - "America/Boa_Vista", - "America/Curacao", - "America/Dominica", - "America/Grand_Turk", - "America/Grenada", - "America/Guadeloupe", - "America/Guyana", - "America/Kralendijk", - "America/La_Paz", - "America/Lower_Princes", - "America/Manaus", - "America/Marigot", - "America/Martinique", - "America/Montserrat", - "America/Port_of_Spain", - "America/Porto_Velho", - "America/Puerto_Rico", - "America/Santo_Domingo", - "America/St_Barthelemy", - "America/St_Kitts", - "America/St_Lucia", - "America/St_Thomas", - "America/St_Vincent", - "America/Tortola", - "Etc/GMT+4" - ] - }, - { - "name": "Pacific SA Standard Time", - "abbr": "PSST", - "offset": -4, - "isdst": false, - "text": "(UTC-04:00) Santiago", - "utc": [ - "America/Santiago", - "Antarctica/Palmer" - ] - }, - { - "name": "Newfoundland Standard Time", - "abbr": "NDT", - "offset": -2.5, - "isdst": true, - "text": "(UTC-03:30) Newfoundland", - "utc": [ - "America/St_Johns" - ] - }, - { - "name": "E. South America Standard Time", - "abbr": "ESAST", - "offset": -3, - "isdst": false, - "text": "(UTC-03:00) Brasilia", - "utc": [ - "America/Sao_Paulo" - ] - }, - { - "name": "Argentina Standard Time", - "abbr": "AST", - "offset": -3, - "isdst": false, - "text": "(UTC-03:00) Buenos Aires", - "utc": [ - "America/Argentina/La_Rioja", - "America/Argentina/Rio_Gallegos", - "America/Argentina/Salta", - "America/Argentina/San_Juan", - "America/Argentina/San_Luis", - "America/Argentina/Tucuman", - "America/Argentina/Ushuaia", - "America/Buenos_Aires", - "America/Catamarca", - "America/Cordoba", - "America/Jujuy", - "America/Mendoza" - ] - }, - { - "name": "SA Eastern Standard Time", - "abbr": "SEST", - "offset": -3, - "isdst": false, - "text": "(UTC-03:00) Cayenne, Fortaleza", - "utc": [ - "America/Araguaina", - "America/Belem", - "America/Cayenne", - "America/Fortaleza", - "America/Maceio", - "America/Paramaribo", - "America/Recife", - "America/Santarem", - "Antarctica/Rothera", - "Atlantic/Stanley", - "Etc/GMT+3" - ] - }, - { - "name": "Greenland Standard Time", - "abbr": "GDT", - "offset": -3, - "isdst": true, - "text": "(UTC-03:00) Greenland", - "utc": [ - "America/Godthab" - ] - }, - { - "name": "Montevideo Standard Time", - "abbr": "MST", - "offset": -3, - "isdst": false, - "text": "(UTC-03:00) Montevideo", - "utc": [ - "America/Montevideo" - ] - }, - { - "name": "Bahia Standard Time", - "abbr": "BST", - "offset": -3, - "isdst": false, - "text": "(UTC-03:00) Salvador", - "utc": [ - "America/Bahia" - ] - }, - { - "name": "UTC-02", - "abbr": "U", - "offset": -2, - "isdst": false, - "text": "(UTC-02:00) Coordinated Universal Time-02", - "utc": [ - "America/Noronha", - "Atlantic/South_Georgia", - "Etc/GMT+2" - ] - }, - { - "name": "Mid-Atlantic Standard Time", - "abbr": "MDT", - "offset": -1, - "isdst": true, - "text": "(UTC-02:00) Mid-Atlantic - Old", - "utc": [] - }, - { - "name": "Azores Standard Time", - "abbr": "ADT", - "offset": 0, - "isdst": true, - "text": "(UTC-01:00) Azores", - "utc": [ - "America/Scoresbysund", - "Atlantic/Azores" - ] - }, - { - "name": "Cape Verde Standard Time", - "abbr": "CVST", - "offset": -1, - "isdst": false, - "text": "(UTC-01:00) Cape Verde Is.", - "utc": [ - "Atlantic/Cape_Verde", - "Etc/GMT+1" - ] - }, - { - "name": "Morocco Standard Time", - "abbr": "MDT", - "offset": 1, - "isdst": true, - "text": "(UTC) Casablanca", - "utc": [ - "Africa/Casablanca", - "Africa/El_Aaiun" - ] - }, - { - "name": "UTC", - "abbr": "UTC", - "offset": 0, - "isdst": false, - "text": "(UTC) Coordinated Universal Time", - "utc": [ - "America/Danmarkshavn", - "Etc/GMT" - ] - }, - { - "name": "GMT Standard Time", - "abbr": "GMT", - "offset": 0, - "isdst": false, - "text": "(UTC) Edinburgh, London", - "utc": [ - "Europe/Isle_of_Man", - "Europe/Guernsey", - "Europe/Jersey", - "Europe/London" - ] - }, - { - "name": "British Summer Time", - "abbr": "BST", - "offset": 1, - "isdst": true, - "text": "(UTC+01:00) Edinburgh, London", - "utc": [ - "Europe/Isle_of_Man", - "Europe/Guernsey", - "Europe/Jersey", - "Europe/London" - ] - }, - { - "name": "GMT Standard Time", - "abbr": "GDT", - "offset": 1, - "isdst": true, - "text": "(UTC) Dublin, Lisbon", - "utc": [ - "Atlantic/Canary", - "Atlantic/Faeroe", - "Atlantic/Madeira", - "Europe/Dublin", - "Europe/Lisbon" - ] - }, - { - "name": "Greenwich Standard Time", - "abbr": "GST", - "offset": 0, - "isdst": false, - "text": "(UTC) Monrovia, Reykjavik", - "utc": [ - "Africa/Abidjan", - "Africa/Accra", - "Africa/Bamako", - "Africa/Banjul", - "Africa/Bissau", - "Africa/Conakry", - "Africa/Dakar", - "Africa/Freetown", - "Africa/Lome", - "Africa/Monrovia", - "Africa/Nouakchott", - "Africa/Ouagadougou", - "Africa/Sao_Tome", - "Atlantic/Reykjavik", - "Atlantic/St_Helena" - ] - }, - { - "name": "W. Europe Standard Time", - "abbr": "WEDT", - "offset": 2, - "isdst": true, - "text": "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna", - "utc": [ - "Arctic/Longyearbyen", - "Europe/Amsterdam", - "Europe/Andorra", - "Europe/Berlin", - "Europe/Busingen", - "Europe/Gibraltar", - "Europe/Luxembourg", - "Europe/Malta", - "Europe/Monaco", - "Europe/Oslo", - "Europe/Rome", - "Europe/San_Marino", - "Europe/Stockholm", - "Europe/Vaduz", - "Europe/Vatican", - "Europe/Vienna", - "Europe/Zurich" - ] - }, - { - "name": "Central Europe Standard Time", - "abbr": "CEDT", - "offset": 2, - "isdst": true, - "text": "(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague", - "utc": [ - "Europe/Belgrade", - "Europe/Bratislava", - "Europe/Budapest", - "Europe/Ljubljana", - "Europe/Podgorica", - "Europe/Prague", - "Europe/Tirane" - ] - }, - { - "name": "Romance Standard Time", - "abbr": "RDT", - "offset": 2, - "isdst": true, - "text": "(UTC+01:00) Brussels, Copenhagen, Madrid, Paris", - "utc": [ - "Africa/Ceuta", - "Europe/Brussels", - "Europe/Copenhagen", - "Europe/Madrid", - "Europe/Paris" - ] - }, - { - "name": "Central European Standard Time", - "abbr": "CEDT", - "offset": 2, - "isdst": true, - "text": "(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb", - "utc": [ - "Europe/Sarajevo", - "Europe/Skopje", - "Europe/Warsaw", - "Europe/Zagreb" - ] - }, - { - "name": "W. Central Africa Standard Time", - "abbr": "WCAST", - "offset": 1, - "isdst": false, - "text": "(UTC+01:00) West Central Africa", - "utc": [ - "Africa/Algiers", - "Africa/Bangui", - "Africa/Brazzaville", - "Africa/Douala", - "Africa/Kinshasa", - "Africa/Lagos", - "Africa/Libreville", - "Africa/Luanda", - "Africa/Malabo", - "Africa/Ndjamena", - "Africa/Niamey", - "Africa/Porto-Novo", - "Africa/Tunis", - "Etc/GMT-1" - ] - }, - { - "name": "Namibia Standard Time", - "abbr": "NST", - "offset": 1, - "isdst": false, - "text": "(UTC+01:00) Windhoek", - "utc": [ - "Africa/Windhoek" - ] - }, - { - "name": "GTB Standard Time", - "abbr": "GDT", - "offset": 3, - "isdst": true, - "text": "(UTC+02:00) Athens, Bucharest", - "utc": [ - "Asia/Nicosia", - "Europe/Athens", - "Europe/Bucharest", - "Europe/Chisinau" - ] - }, - { - "name": "Middle East Standard Time", - "abbr": "MEDT", - "offset": 3, - "isdst": true, - "text": "(UTC+02:00) Beirut", - "utc": [ - "Asia/Beirut" - ] - }, - { - "name": "Egypt Standard Time", - "abbr": "EST", - "offset": 2, - "isdst": false, - "text": "(UTC+02:00) Cairo", - "utc": [ - "Africa/Cairo" - ] - }, - { - "name": "Syria Standard Time", - "abbr": "SDT", - "offset": 3, - "isdst": true, - "text": "(UTC+02:00) Damascus", - "utc": [ - "Asia/Damascus" - ] - }, - { - "name": "E. Europe Standard Time", - "abbr": "EEDT", - "offset": 3, - "isdst": true, - "text": "(UTC+02:00) E. Europe", - "utc": [ - "Asia/Nicosia", - "Europe/Athens", - "Europe/Bucharest", - "Europe/Chisinau", - "Europe/Helsinki", - "Europe/Kiev", - "Europe/Mariehamn", - "Europe/Nicosia", - "Europe/Riga", - "Europe/Sofia", - "Europe/Tallinn", - "Europe/Uzhgorod", - "Europe/Vilnius", - "Europe/Zaporozhye" - ] - }, - { - "name": "South Africa Standard Time", - "abbr": "SAST", - "offset": 2, - "isdst": false, - "text": "(UTC+02:00) Harare, Pretoria", - "utc": [ - "Africa/Blantyre", - "Africa/Bujumbura", - "Africa/Gaborone", - "Africa/Harare", - "Africa/Johannesburg", - "Africa/Kigali", - "Africa/Lubumbashi", - "Africa/Lusaka", - "Africa/Maputo", - "Africa/Maseru", - "Africa/Mbabane", - "Etc/GMT-2" - ] - }, - { - "name": "FLE Standard Time", - "abbr": "FDT", - "offset": 3, - "isdst": true, - "text": "(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius", - "utc": [ - "Europe/Helsinki", - "Europe/Kiev", - "Europe/Mariehamn", - "Europe/Riga", - "Europe/Sofia", - "Europe/Tallinn", - "Europe/Uzhgorod", - "Europe/Vilnius", - "Europe/Zaporozhye" - ] - }, - { - "name": "Turkey Standard Time", - "abbr": "TDT", - "offset": 3, - "isdst": false, - "text": "(UTC+03:00) Istanbul", - "utc": [ - "Europe/Istanbul" - ] - }, - { - "name": "Israel Standard Time", - "abbr": "JDT", - "offset": 3, - "isdst": true, - "text": "(UTC+02:00) Jerusalem", - "utc": [ - "Asia/Jerusalem" - ] - }, - { - "name": "Libya Standard Time", - "abbr": "LST", - "offset": 2, - "isdst": false, - "text": "(UTC+02:00) Tripoli", - "utc": [ - "Africa/Tripoli" - ] - }, - { - "name": "Jordan Standard Time", - "abbr": "JST", - "offset": 3, - "isdst": false, - "text": "(UTC+03:00) Amman", - "utc": [ - "Asia/Amman" - ] - }, - { - "name": "Arabic Standard Time", - "abbr": "AST", - "offset": 3, - "isdst": false, - "text": "(UTC+03:00) Baghdad", - "utc": [ - "Asia/Baghdad" - ] - }, - { - "name": "Kaliningrad Standard Time", - "abbr": "KST", - "offset": 3, - "isdst": false, - "text": "(UTC+02:00) Kaliningrad", - "utc": [ - "Europe/Kaliningrad" - ] - }, - { - "name": "Arab Standard Time", - "abbr": "AST", - "offset": 3, - "isdst": false, - "text": "(UTC+03:00) Kuwait, Riyadh", - "utc": [ - "Asia/Aden", - "Asia/Bahrain", - "Asia/Kuwait", - "Asia/Qatar", - "Asia/Riyadh" - ] - }, - { - "name": "E. Africa Standard Time", - "abbr": "EAST", - "offset": 3, - "isdst": false, - "text": "(UTC+03:00) Nairobi", - "utc": [ - "Africa/Addis_Ababa", - "Africa/Asmera", - "Africa/Dar_es_Salaam", - "Africa/Djibouti", - "Africa/Juba", - "Africa/Kampala", - "Africa/Khartoum", - "Africa/Mogadishu", - "Africa/Nairobi", - "Antarctica/Syowa", - "Etc/GMT-3", - "Indian/Antananarivo", - "Indian/Comoro", - "Indian/Mayotte" - ] - }, - { - "name": "Moscow Standard Time", - "abbr": "MSK", - "offset": 3, - "isdst": false, - "text": "(UTC+03:00) Moscow, St. Petersburg, Volgograd, Minsk", - "utc": [ - "Europe/Kirov", - "Europe/Moscow", - "Europe/Simferopol", - "Europe/Volgograd", - "Europe/Minsk" - ] - }, - { - "name": "Samara Time", - "abbr": "SAMT", - "offset": 4, - "isdst": false, - "text": "(UTC+04:00) Samara, Ulyanovsk, Saratov", - "utc": [ - "Europe/Astrakhan", - "Europe/Samara", - "Europe/Ulyanovsk" - ] - }, - { - "name": "Iran Standard Time", - "abbr": "IDT", - "offset": 4.5, - "isdst": true, - "text": "(UTC+03:30) Tehran", - "utc": [ - "Asia/Tehran" - ] - }, - { - "name": "Arabian Standard Time", - "abbr": "AST", - "offset": 4, - "isdst": false, - "text": "(UTC+04:00) Abu Dhabi, Muscat", - "utc": [ - "Asia/Dubai", - "Asia/Muscat", - "Etc/GMT-4" - ] - }, - { - "name": "Azerbaijan Standard Time", - "abbr": "ADT", - "offset": 5, - "isdst": true, - "text": "(UTC+04:00) Baku", - "utc": [ - "Asia/Baku" - ] - }, - { - "name": "Mauritius Standard Time", - "abbr": "MST", - "offset": 4, - "isdst": false, - "text": "(UTC+04:00) Port Louis", - "utc": [ - "Indian/Mahe", - "Indian/Mauritius", - "Indian/Reunion" - ] - }, - { - "name": "Georgian Standard Time", - "abbr": "GET", - "offset": 4, - "isdst": false, - "text": "(UTC+04:00) Tbilisi", - "utc": [ - "Asia/Tbilisi" - ] - }, - { - "name": "Caucasus Standard Time", - "abbr": "CST", - "offset": 4, - "isdst": false, - "text": "(UTC+04:00) Yerevan", - "utc": [ - "Asia/Yerevan" - ] - }, - { - "name": "Afghanistan Standard Time", - "abbr": "AST", - "offset": 4.5, - "isdst": false, - "text": "(UTC+04:30) Kabul", - "utc": [ - "Asia/Kabul" - ] - }, - { - "name": "West Asia Standard Time", - "abbr": "WAST", - "offset": 5, - "isdst": false, - "text": "(UTC+05:00) Ashgabat, Tashkent", - "utc": [ - "Antarctica/Mawson", - "Asia/Aqtau", - "Asia/Aqtobe", - "Asia/Ashgabat", - "Asia/Dushanbe", - "Asia/Oral", - "Asia/Samarkand", - "Asia/Tashkent", - "Etc/GMT-5", - "Indian/Kerguelen", - "Indian/Maldives" - ] - }, - { - "name": "Yekaterinburg Time", - "abbr": "YEKT", - "offset": 5, - "isdst": false, - "text": "(UTC+05:00) Yekaterinburg", - "utc": [ - "Asia/Yekaterinburg" - ] - }, - { - "name": "Pakistan Standard Time", - "abbr": "PKT", - "offset": 5, - "isdst": false, - "text": "(UTC+05:00) Islamabad, Karachi", - "utc": [ - "Asia/Karachi" - ] - }, - { - "name": "India Standard Time", - "abbr": "IST", - "offset": 5.5, - "isdst": false, - "text": "(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi", - "utc": [ - "Asia/Kolkata" - ] - }, - { - "name": "Sri Lanka Standard Time", - "abbr": "SLST", - "offset": 5.5, - "isdst": false, - "text": "(UTC+05:30) Sri Jayawardenepura", - "utc": [ - "Asia/Colombo" - ] - }, - { - "name": "Nepal Standard Time", - "abbr": "NST", - "offset": 5.75, - "isdst": false, - "text": "(UTC+05:45) Kathmandu", - "utc": [ - "Asia/Kathmandu" - ] - }, - { - "name": "Central Asia Standard Time", - "abbr": "CAST", - "offset": 6, - "isdst": false, - "text": "(UTC+06:00) Nur-Sultan (Astana)", - "utc": [ - "Antarctica/Vostok", - "Asia/Almaty", - "Asia/Bishkek", - "Asia/Qyzylorda", - "Asia/Urumqi", - "Etc/GMT-6", - "Indian/Chagos" - ] - }, - { - "name": "Bangladesh Standard Time", - "abbr": "BST", - "offset": 6, - "isdst": false, - "text": "(UTC+06:00) Dhaka", - "utc": [ - "Asia/Dhaka", - "Asia/Thimphu" - ] - }, - { - "name": "Myanmar Standard Time", - "abbr": "MST", - "offset": 6.5, - "isdst": false, - "text": "(UTC+06:30) Yangon (Rangoon)", - "utc": [ - "Asia/Rangoon", - "Indian/Cocos" - ] - }, - { - "name": "SE Asia Standard Time", - "abbr": "SAST", - "offset": 7, - "isdst": false, - "text": "(UTC+07:00) Bangkok, Hanoi, Jakarta", - "utc": [ - "Antarctica/Davis", - "Asia/Bangkok", - "Asia/Hovd", - "Asia/Jakarta", - "Asia/Phnom_Penh", - "Asia/Pontianak", - "Asia/Saigon", - "Asia/Vientiane", - "Etc/GMT-7", - "Indian/Christmas" - ] - }, - { - "name": "N. Central Asia Standard Time", - "abbr": "NCAST", - "offset": 7, - "isdst": false, - "text": "(UTC+07:00) Novosibirsk", - "utc": [ - "Asia/Novokuznetsk", - "Asia/Novosibirsk", - "Asia/Omsk" - ] - }, - { - "name": "China Standard Time", - "abbr": "CST", - "offset": 8, - "isdst": false, - "text": "(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi", - "utc": [ - "Asia/Hong_Kong", - "Asia/Macau", - "Asia/Shanghai" - ] - }, - { - "name": "North Asia Standard Time", - "abbr": "NAST", - "offset": 8, - "isdst": false, - "text": "(UTC+08:00) Krasnoyarsk", - "utc": [ - "Asia/Krasnoyarsk" - ] - }, - { - "name": "Singapore Standard Time", - "abbr": "MPST", - "offset": 8, - "isdst": false, - "text": "(UTC+08:00) Kuala Lumpur, Singapore", - "utc": [ - "Asia/Brunei", - "Asia/Kuala_Lumpur", - "Asia/Kuching", - "Asia/Makassar", - "Asia/Manila", - "Asia/Singapore", - "Etc/GMT-8" - ] - }, - { - "name": "W. Australia Standard Time", - "abbr": "WAST", - "offset": 8, - "isdst": false, - "text": "(UTC+08:00) Perth", - "utc": [ - "Antarctica/Casey", - "Australia/Perth" - ] - }, - { - "name": "Taipei Standard Time", - "abbr": "TST", - "offset": 8, - "isdst": false, - "text": "(UTC+08:00) Taipei", - "utc": [ - "Asia/Taipei" - ] - }, - { - "name": "Ulaanbaatar Standard Time", - "abbr": "UST", - "offset": 8, - "isdst": false, - "text": "(UTC+08:00) Ulaanbaatar", - "utc": [ - "Asia/Choibalsan", - "Asia/Ulaanbaatar" - ] - }, - { - "name": "North Asia East Standard Time", - "abbr": "NAEST", - "offset": 8, - "isdst": false, - "text": "(UTC+08:00) Irkutsk", - "utc": [ - "Asia/Irkutsk" - ] - }, - { - "name": "Japan Standard Time", - "abbr": "JST", - "offset": 9, - "isdst": false, - "text": "(UTC+09:00) Osaka, Sapporo, Tokyo", - "utc": [ - "Asia/Dili", - "Asia/Jayapura", - "Asia/Tokyo", - "Etc/GMT-9", - "Pacific/Palau" - ] - }, - { - "name": "Korea Standard Time", - "abbr": "KST", - "offset": 9, - "isdst": false, - "text": "(UTC+09:00) Seoul", - "utc": [ - "Asia/Pyongyang", - "Asia/Seoul" - ] - }, - { - "name": "Cen. Australia Standard Time", - "abbr": "CAST", - "offset": 9.5, - "isdst": false, - "text": "(UTC+09:30) Adelaide", - "utc": [ - "Australia/Adelaide", - "Australia/Broken_Hill" - ] - }, - { - "name": "AUS Central Standard Time", - "abbr": "ACST", - "offset": 9.5, - "isdst": false, - "text": "(UTC+09:30) Darwin", - "utc": [ - "Australia/Darwin" - ] - }, - { - "name": "E. Australia Standard Time", - "abbr": "EAST", - "offset": 10, - "isdst": false, - "text": "(UTC+10:00) Brisbane", - "utc": [ - "Australia/Brisbane", - "Australia/Lindeman" - ] - }, - { - "name": "AUS Eastern Standard Time", - "abbr": "AEST", - "offset": 10, - "isdst": false, - "text": "(UTC+10:00) Canberra, Melbourne, Sydney", - "utc": [ - "Australia/Melbourne", - "Australia/Sydney" - ] - }, - { - "name": "West Pacific Standard Time", - "abbr": "WPST", - "offset": 10, - "isdst": false, - "text": "(UTC+10:00) Guam, Port Moresby", - "utc": [ - "Antarctica/DumontDUrville", - "Etc/GMT-10", - "Pacific/Guam", - "Pacific/Port_Moresby", - "Pacific/Saipan", - "Pacific/Truk" - ] - }, - { - "name": "Tasmania Standard Time", - "abbr": "TST", - "offset": 10, - "isdst": false, - "text": "(UTC+10:00) Hobart", - "utc": [ - "Australia/Currie", - "Australia/Hobart" - ] - }, - { - "name": "Yakutsk Standard Time", - "abbr": "YST", - "offset": 9, - "isdst": false, - "text": "(UTC+09:00) Yakutsk", - "utc": [ - "Asia/Chita", - "Asia/Khandyga", - "Asia/Yakutsk" - ] - }, - { - "name": "Central Pacific Standard Time", - "abbr": "CPST", - "offset": 11, - "isdst": false, - "text": "(UTC+11:00) Solomon Is., New Caledonia", - "utc": [ - "Antarctica/Macquarie", - "Etc/GMT-11", - "Pacific/Efate", - "Pacific/Guadalcanal", - "Pacific/Kosrae", - "Pacific/Noumea", - "Pacific/Ponape" - ] - }, - { - "name": "Vladivostok Standard Time", - "abbr": "VST", - "offset": 11, - "isdst": false, - "text": "(UTC+11:00) Vladivostok", - "utc": [ - "Asia/Sakhalin", - "Asia/Ust-Nera", - "Asia/Vladivostok" - ] - }, - { - "name": "New Zealand Standard Time", - "abbr": "NZST", - "offset": 12, - "isdst": false, - "text": "(UTC+12:00) Auckland, Wellington", - "utc": [ - "Antarctica/McMurdo", - "Pacific/Auckland" - ] - }, - { - "name": "UTC+12", - "abbr": "U", - "offset": 12, - "isdst": false, - "text": "(UTC+12:00) Coordinated Universal Time+12", - "utc": [ - "Etc/GMT-12", - "Pacific/Funafuti", - "Pacific/Kwajalein", - "Pacific/Majuro", - "Pacific/Nauru", - "Pacific/Tarawa", - "Pacific/Wake", - "Pacific/Wallis" - ] - }, - { - "name": "Fiji Standard Time", - "abbr": "FST", - "offset": 12, - "isdst": false, - "text": "(UTC+12:00) Fiji", - "utc": [ - "Pacific/Fiji" - ] - }, - { - "name": "Magadan Standard Time", - "abbr": "MST", - "offset": 12, - "isdst": false, - "text": "(UTC+12:00) Magadan", - "utc": [ - "Asia/Anadyr", - "Asia/Kamchatka", - "Asia/Magadan", - "Asia/Srednekolymsk" - ] - }, - { - "name": "Kamchatka Standard Time", - "abbr": "KDT", - "offset": 13, - "isdst": true, - "text": "(UTC+12:00) Petropavlovsk-Kamchatsky - Old", - "utc": [ - "Asia/Kamchatka" - ] - }, - { - "name": "Tonga Standard Time", - "abbr": "TST", - "offset": 13, - "isdst": false, - "text": "(UTC+13:00) Nuku'alofa", - "utc": [ - "Etc/GMT-13", - "Pacific/Enderbury", - "Pacific/Fakaofo", - "Pacific/Tongatapu" - ] - }, - { - "name": "Samoa Standard Time", - "abbr": "SST", - "offset": 13, - "isdst": false, - "text": "(UTC+13:00) Samoa", - "utc": [ - "Pacific/Apia" - ] - } - ], - //List source: http://answers.google.com/answers/threadview/id/589312.html - profession: [ - "Airline Pilot", - "Academic Team", - "Accountant", - "Account Executive", - "Actor", - "Actuary", - "Acquisition Analyst", - "Administrative Asst.", - "Administrative Analyst", - "Administrator", - "Advertising Director", - "Aerospace Engineer", - "Agent", - "Agricultural Inspector", - "Agricultural Scientist", - "Air Traffic Controller", - "Animal Trainer", - "Anthropologist", - "Appraiser", - "Architect", - "Art Director", - "Artist", - "Astronomer", - "Athletic Coach", - "Auditor", - "Author", - "Baker", - "Banker", - "Bankruptcy Attorney", - "Benefits Manager", - "Biologist", - "Bio-feedback Specialist", - "Biomedical Engineer", - "Biotechnical Researcher", - "Broadcaster", - "Broker", - "Building Manager", - "Building Contractor", - "Building Inspector", - "Business Analyst", - "Business Planner", - "Business Manager", - "Buyer", - "Call Center Manager", - "Career Counselor", - "Cash Manager", - "Ceramic Engineer", - "Chief Executive Officer", - "Chief Operation Officer", - "Chef", - "Chemical Engineer", - "Chemist", - "Child Care Manager", - "Chief Medical Officer", - "Chiropractor", - "Cinematographer", - "City Housing Manager", - "City Manager", - "Civil Engineer", - "Claims Manager", - "Clinical Research Assistant", - "Collections Manager", - "Compliance Manager", - "Comptroller", - "Computer Manager", - "Commercial Artist", - "Communications Affairs Director", - "Communications Director", - "Communications Engineer", - "Compensation Analyst", - "Computer Programmer", - "Computer Ops. Manager", - "Computer Engineer", - "Computer Operator", - "Computer Graphics Specialist", - "Construction Engineer", - "Construction Manager", - "Consultant", - "Consumer Relations Manager", - "Contract Administrator", - "Copyright Attorney", - "Copywriter", - "Corporate Planner", - "Corrections Officer", - "Cosmetologist", - "Credit Analyst", - "Cruise Director", - "Chief Information Officer", - "Chief Technology Officer", - "Customer Service Manager", - "Cryptologist", - "Dancer", - "Data Security Manager", - "Database Manager", - "Day Care Instructor", - "Dentist", - "Designer", - "Design Engineer", - "Desktop Publisher", - "Developer", - "Development Officer", - "Diamond Merchant", - "Dietitian", - "Direct Marketer", - "Director", - "Distribution Manager", - "Diversity Manager", - "Economist", - "EEO Compliance Manager", - "Editor", - "Education Adminator", - "Electrical Engineer", - "Electro Optical Engineer", - "Electronics Engineer", - "Embassy Management", - "Employment Agent", - "Engineer Technician", - "Entrepreneur", - "Environmental Analyst", - "Environmental Attorney", - "Environmental Engineer", - "Environmental Specialist", - "Escrow Officer", - "Estimator", - "Executive Assistant", - "Executive Director", - "Executive Recruiter", - "Facilities Manager", - "Family Counselor", - "Fashion Events Manager", - "Fashion Merchandiser", - "Fast Food Manager", - "Film Producer", - "Film Production Assistant", - "Financial Analyst", - "Financial Planner", - "Financier", - "Fine Artist", - "Wildlife Specialist", - "Fitness Consultant", - "Flight Attendant", - "Flight Engineer", - "Floral Designer", - "Food & Beverage Director", - "Food Service Manager", - "Forestry Technician", - "Franchise Management", - "Franchise Sales", - "Fraud Investigator", - "Freelance Writer", - "Fund Raiser", - "General Manager", - "Geologist", - "General Counsel", - "Geriatric Specialist", - "Gerontologist", - "Glamour Photographer", - "Golf Club Manager", - "Gourmet Chef", - "Graphic Designer", - "Grounds Keeper", - "Hazardous Waste Manager", - "Health Care Manager", - "Health Therapist", - "Health Service Administrator", - "Hearing Officer", - "Home Economist", - "Horticulturist", - "Hospital Administrator", - "Hotel Manager", - "Human Resources Manager", - "Importer", - "Industrial Designer", - "Industrial Engineer", - "Information Director", - "Inside Sales", - "Insurance Adjuster", - "Interior Decorator", - "Internal Controls Director", - "International Acct.", - "International Courier", - "International Lawyer", - "Interpreter", - "Investigator", - "Investment Banker", - "Investment Manager", - "IT Architect", - "IT Project Manager", - "IT Systems Analyst", - "Jeweler", - "Joint Venture Manager", - "Journalist", - "Labor Negotiator", - "Labor Organizer", - "Labor Relations Manager", - "Lab Services Director", - "Lab Technician", - "Land Developer", - "Landscape Architect", - "Law Enforcement Officer", - "Lawyer", - "Lead Software Engineer", - "Lead Software Test Engineer", - "Leasing Manager", - "Legal Secretary", - "Library Manager", - "Litigation Attorney", - "Loan Officer", - "Lobbyist", - "Logistics Manager", - "Maintenance Manager", - "Management Consultant", - "Managed Care Director", - "Managing Partner", - "Manufacturing Director", - "Manpower Planner", - "Marine Biologist", - "Market Res. Analyst", - "Marketing Director", - "Materials Manager", - "Mathematician", - "Membership Chairman", - "Mechanic", - "Mechanical Engineer", - "Media Buyer", - "Medical Investor", - "Medical Secretary", - "Medical Technician", - "Mental Health Counselor", - "Merchandiser", - "Metallurgical Engineering", - "Meteorologist", - "Microbiologist", - "MIS Manager", - "Motion Picture Director", - "Multimedia Director", - "Musician", - "Network Administrator", - "Network Specialist", - "Network Operator", - "New Product Manager", - "Novelist", - "Nuclear Engineer", - "Nuclear Specialist", - "Nutritionist", - "Nursing Administrator", - "Occupational Therapist", - "Oceanographer", - "Office Manager", - "Operations Manager", - "Operations Research Director", - "Optical Technician", - "Optometrist", - "Organizational Development Manager", - "Outplacement Specialist", - "Paralegal", - "Park Ranger", - "Patent Attorney", - "Payroll Specialist", - "Personnel Specialist", - "Petroleum Engineer", - "Pharmacist", - "Photographer", - "Physical Therapist", - "Physician", - "Physician Assistant", - "Physicist", - "Planning Director", - "Podiatrist", - "Political Analyst", - "Political Scientist", - "Politician", - "Portfolio Manager", - "Preschool Management", - "Preschool Teacher", - "Principal", - "Private Banker", - "Private Investigator", - "Probation Officer", - "Process Engineer", - "Producer", - "Product Manager", - "Product Engineer", - "Production Engineer", - "Production Planner", - "Professional Athlete", - "Professional Coach", - "Professor", - "Project Engineer", - "Project Manager", - "Program Manager", - "Property Manager", - "Public Administrator", - "Public Safety Director", - "PR Specialist", - "Publisher", - "Purchasing Agent", - "Publishing Director", - "Quality Assurance Specialist", - "Quality Control Engineer", - "Quality Control Inspector", - "Radiology Manager", - "Railroad Engineer", - "Real Estate Broker", - "Recreational Director", - "Recruiter", - "Redevelopment Specialist", - "Regulatory Affairs Manager", - "Registered Nurse", - "Rehabilitation Counselor", - "Relocation Manager", - "Reporter", - "Research Specialist", - "Restaurant Manager", - "Retail Store Manager", - "Risk Analyst", - "Safety Engineer", - "Sales Engineer", - "Sales Trainer", - "Sales Promotion Manager", - "Sales Representative", - "Sales Manager", - "Service Manager", - "Sanitation Engineer", - "Scientific Programmer", - "Scientific Writer", - "Securities Analyst", - "Security Consultant", - "Security Director", - "Seminar Presenter", - "Ship's Officer", - "Singer", - "Social Director", - "Social Program Planner", - "Social Research", - "Social Scientist", - "Social Worker", - "Sociologist", - "Software Developer", - "Software Engineer", - "Software Test Engineer", - "Soil Scientist", - "Special Events Manager", - "Special Education Teacher", - "Special Projects Director", - "Speech Pathologist", - "Speech Writer", - "Sports Event Manager", - "Statistician", - "Store Manager", - "Strategic Alliance Director", - "Strategic Planning Director", - "Stress Reduction Specialist", - "Stockbroker", - "Surveyor", - "Structural Engineer", - "Superintendent", - "Supply Chain Director", - "System Engineer", - "Systems Analyst", - "Systems Programmer", - "System Administrator", - "Tax Specialist", - "Teacher", - "Technical Support Specialist", - "Technical Illustrator", - "Technical Writer", - "Technology Director", - "Telecom Analyst", - "Telemarketer", - "Theatrical Director", - "Title Examiner", - "Tour Escort", - "Tour Guide Director", - "Traffic Manager", - "Trainer Translator", - "Transportation Manager", - "Travel Agent", - "Treasurer", - "TV Programmer", - "Underwriter", - "Union Representative", - "University Administrator", - "University Dean", - "Urban Planner", - "Veterinarian", - "Vendor Relations Director", - "Viticulturist", - "Warehouse Manager" - ], - animals : { - //list of ocean animals comes from https://owlcation.com/stem/list-of-ocean-animals - "ocean" : ["Acantharea","Anemone","Angelfish King","Ahi Tuna","Albacore","American Oyster","Anchovy","Armored Snail","Arctic Char","Atlantic Bluefin Tuna","Atlantic Cod","Atlantic Goliath Grouper","Atlantic Trumpetfish","Atlantic Wolffish","Baleen Whale","Banded Butterflyfish","Banded Coral Shrimp","Banded Sea Krait","Barnacle","Barndoor Skate","Barracuda","Basking Shark","Bass","Beluga Whale","Bluebanded Goby","Bluehead Wrasse","Bluefish","Bluestreak Cleaner-Wrasse","Blue Marlin","Blue Shark","Blue Spiny Lobster","Blue Tang","Blue Whale","Broadclub Cuttlefish","Bull Shark","Chambered Nautilus","Chilean Basket Star","Chilean Jack Mackerel","Chinook Salmon","Christmas Tree Worm","Clam","Clown Anemonefish","Clown Triggerfish","Cod","Coelacanth","Cockscomb Cup Coral","Common Fangtooth","Conch","Cookiecutter Shark","Copepod","Coral","Corydoras","Cownose Ray","Crab","Crown-of-Thorns Starfish","Cushion Star","Cuttlefish","California Sea Otters","Dolphin","Dolphinfish","Dory","Devil Fish","Dugong","Dumbo Octopus","Dungeness Crab","Eccentric Sand Dollar","Edible Sea Cucumber","Eel","Elephant Seal","Elkhorn Coral","Emperor Shrimp","Estuarine Crocodile","Fathead Sculpin","Fiddler Crab","Fin Whale","Flameback","Flamingo Tongue Snail","Flashlight Fish","Flatback Turtle","Flatfish","Flying Fish","Flounder","Fluke","French Angelfish","Frilled Shark","Fugu (also called Pufferfish)","Gar","Geoduck","Giant Barrel Sponge","Giant Caribbean Sea Anemone","Giant Clam","Giant Isopod","Giant Kingfish","Giant Oarfish","Giant Pacific Octopus","Giant Pyrosome","Giant Sea Star","Giant Squid","Glowing Sucker Octopus","Giant Tube Worm","Goblin Shark","Goosefish","Great White Shark","Greenland Shark","Grey Atlantic Seal","Grouper","Grunion","Guineafowl Puffer","Haddock","Hake","Halibut","Hammerhead Shark","Hapuka","Harbor Porpoise","Harbor Seal","Hatchetfish","Hawaiian Monk Seal","Hawksbill Turtle","Hector's Dolphin","Hermit Crab","Herring","Hoki","Horn Shark","Horseshoe Crab","Humpback Anglerfish","Humpback Whale","Icefish","Imperator Angelfish","Irukandji Jellyfish","Isopod","Ivory Bush Coral","Japanese Spider Crab","Jellyfish","John Dory","Juan Fernandez Fur Seal","Killer Whale","Kiwa Hirsuta","Krill","Lagoon Triggerfish","Lamprey","Leafy Seadragon","Leopard Seal","Limpet","Ling","Lionfish","Lions Mane Jellyfish","Lobe Coral","Lobster","Loggerhead Turtle","Longnose Sawshark","Longsnout Seahorse","Lophelia Coral","Marrus Orthocanna","Manatee","Manta Ray","Marlin","Megamouth Shark","Mexican Lookdown","Mimic Octopus","Moon Jelly","Mollusk","Monkfish","Moray Eel","Mullet","Mussel","Megaladon","Napoleon Wrasse","Nassau Grouper","Narwhal","Nautilus","Needlefish","Northern Seahorse","North Atlantic Right Whale","Northern Red Snapper","Norway Lobster","Nudibranch","Nurse Shark","Oarfish","Ocean Sunfish","Oceanic Whitetip Shark","Octopus","Olive Sea Snake","Orange Roughy","Ostracod","Otter","Oyster","Pacific Angelshark","Pacific Blackdragon","Pacific Halibut","Pacific Sardine","Pacific Sea Nettle Jellyfish","Pacific White Sided Dolphin","Pantropical Spotted Dolphin","Patagonian Toothfish","Peacock Mantis Shrimp","Pelagic Thresher Shark","Penguin","Peruvian Anchoveta","Pilchard","Pink Salmon","Pinniped","Plankton","Porpoise","Polar Bear","Portuguese Man o' War","Pycnogonid Sea Spider","Quahog","Queen Angelfish","Queen Conch","Queen Parrotfish","Queensland Grouper","Ragfish","Ratfish","Rattail Fish","Ray","Red Drum","Red King Crab","Ringed Seal","Risso's Dolphin","Ross Seals","Sablefish","Salmon","Sand Dollar","Sandbar Shark","Sawfish","Sarcastic Fringehead","Scalloped Hammerhead Shark","Seahorse","Sea Cucumber","Sea Lion","Sea Urchin","Seal","Shark","Shortfin Mako Shark","Shovelnose Guitarfish","Shrimp","Silverside Fish","Skipjack Tuna","Slender Snipe Eel","Smalltooth Sawfish","Smelts","Sockeye Salmon","Southern Stingray","Sponge","Spotted Porcupinefish","Spotted Dolphin","Spotted Eagle Ray","Spotted Moray","Squid","Squidworm","Starfish","Stickleback","Stonefish","Stoplight Loosejaw","Sturgeon","Swordfish","Tan Bristlemouth","Tasseled Wobbegong","Terrible Claw Lobster","Threespot Damselfish","Tiger Prawn","Tiger Shark","Tilefish","Toadfish","Tropical Two-Wing Flyfish","Tuna","Umbrella Squid","Velvet Crab","Venus Flytrap Sea Anemone","Vigtorniella Worm","Viperfish","Vampire Squid","Vaquita","Wahoo","Walrus","West Indian Manatee","Whale","Whale Shark","Whiptail Gulper","White-Beaked Dolphin","White-Ring Garden Eel","White Shrimp","Wobbegong","Wrasse","Wreckfish","Xiphosura","Yellowtail Damselfish","Yelloweye Rockfish","Yellow Cup Black Coral","Yellow Tube Sponge","Yellowfin Tuna","Zebrashark","Zooplankton"], - //list of desert, grassland, and forest animals comes from http://www.skyenimals.com/ - "desert" : ["Aardwolf","Addax","African Wild Ass","Ant","Antelope","Armadillo","Baboon","Badger","Bat","Bearded Dragon","Beetle","Bird","Black-footed Cat","Boa","Brown Bear","Bustard","Butterfly","Camel","Caracal","Caracara","Caterpillar","Centipede","Cheetah","Chipmunk","Chuckwalla","Climbing Mouse","Coati","Cobra","Cotton Rat","Cougar","Courser","Crane Fly","Crow","Dassie Rat","Dove","Dunnart","Eagle","Echidna","Elephant","Emu","Falcon","Fly","Fox","Frogmouth","Gecko","Geoffroy's Cat","Gerbil","Grasshopper","Guanaco","Gundi","Hamster","Hawk","Hedgehog","Hyena","Hyrax","Jackal","Kangaroo","Kangaroo Rat","Kestrel","Kowari","Kultarr","Leopard","Lion","Macaw","Meerkat","Mouse","Oryx","Ostrich","Owl","Pronghorn","Python","Rabbit","Raccoon","Rattlesnake","Rhinoceros","Sand Cat","Spectacled Bear","Spiny Mouse","Starling","Stick Bug","Tarantula","Tit","Toad","Tortoise","Tyrant Flycatcher","Viper","Vulture","Waxwing","Xerus","Zebra"], - "grassland" : ["Aardvark","Aardwolf","Accentor","African Buffalo","African Wild Dog","Alpaca","Anaconda","Ant","Anteater","Antelope","Armadillo","Baboon","Badger","Bandicoot","Barbet","Bat","Bee","Bee-eater","Beetle","Bird","Bison","Black-footed Cat","Black-footed Ferret","Bluebird","Boa","Bowerbird","Brown Bear","Bush Dog","Bushshrike","Bustard","Butterfly","Buzzard","Caracal","Caracara","Cardinal","Caterpillar","Cheetah","Chipmunk","Civet","Climbing Mouse","Clouded Leopard","Coati","Cobra","Cockatoo","Cockroach","Common Genet","Cotton Rat","Cougar","Courser","Coyote","Crane","Crane Fly","Cricket","Crow","Culpeo","Death Adder","Deer","Deer Mouse","Dingo","Dinosaur","Dove","Drongo","Duck","Duiker","Dunnart","Eagle","Echidna","Elephant","Elk","Emu","Falcon","Finch","Flea","Fly","Flying Frog","Fox","Frog","Frogmouth","Garter Snake","Gazelle","Gecko","Geoffroy's Cat","Gerbil","Giant Tortoise","Giraffe","Grasshopper","Grison","Groundhog","Grouse","Guanaco","Guinea Pig","Hamster","Harrier","Hartebeest","Hawk","Hedgehog","Helmetshrike","Hippopotamus","Hornbill","Hyena","Hyrax","Impala","Jackal","Jaguar","Jaguarundi","Kangaroo","Kangaroo Rat","Kestrel","Kultarr","Ladybug","Leopard","Lion","Macaw","Meerkat","Mouse","Newt","Oryx","Ostrich","Owl","Pangolin","Pheasant","Prairie Dog","Pronghorn","Przewalski's Horse","Python","Quoll","Rabbit","Raven","Rhinoceros","Shelduck","Sloth Bear","Spectacled Bear","Squirrel","Starling","Stick Bug","Tamandua","Tasmanian Devil","Thornbill","Thrush","Toad","Tortoise"], - "forest" : ["Agouti","Anaconda","Anoa","Ant","Anteater","Antelope","Armadillo","Asian Black Bear","Aye-aye","Babirusa","Baboon","Badger","Bandicoot","Banteng","Barbet","Basilisk","Bat","Bearded Dragon","Bee","Bee-eater","Beetle","Bettong","Binturong","Bird-of-paradise","Bongo","Bowerbird","Bulbul","Bush Dog","Bushbaby","Bushshrike","Butterfly","Buzzard","Caecilian","Cardinal","Cassowary","Caterpillar","Centipede","Chameleon","Chimpanzee","Cicada","Civet","Clouded Leopard","Coati","Cobra","Cockatoo","Cockroach","Colugo","Cotinga","Cotton Rat","Cougar","Crane Fly","Cricket","Crocodile","Crow","Cuckoo","Cuscus","Death Adder","Deer","Dhole","Dingo","Dinosaur","Drongo","Duck","Duiker","Eagle","Echidna","Elephant","Finch","Flat-headed Cat","Flea","Flowerpecker","Fly","Flying Frog","Fossa","Frog","Frogmouth","Gaur","Gecko","Gorilla","Grison","Hawaiian Honeycreeper","Hawk","Hedgehog","Helmetshrike","Hornbill","Hyrax","Iguana","Jackal","Jaguar","Jaguarundi","Kestrel","Ladybug","Lemur","Leopard","Lion","Macaw","Mandrill","Margay","Monkey","Mouse","Mouse Deer","Newt","Okapi","Old World Flycatcher","Orangutan","Owl","Pangolin","Peafowl","Pheasant","Possum","Python","Quokka","Rabbit","Raccoon","Red Panda","Red River Hog","Rhinoceros","Sloth Bear","Spectacled Bear","Squirrel","Starling","Stick Bug","Sun Bear","Tamandua","Tamarin","Tapir","Tarantula","Thrush","Tiger","Tit","Toad","Tortoise","Toucan","Trogon","Trumpeter","Turaco","Turtle","Tyrant Flycatcher","Viper","Vulture","Wallaby","Warbler","Wasp","Waxwing","Weaver","Weaver-finch","Whistler","White-eye","Whydah","Woodswallow","Worm","Wren","Xenops","Yellowjacket","Accentor","African Buffalo","American Black Bear","Anole","Bird","Bison","Boa","Brown Bear","Chipmunk","Common Genet","Copperhead","Coyote","Deer Mouse","Dormouse","Elk","Emu","Fisher","Fox","Garter Snake","Giant Panda","Giant Tortoise","Groundhog","Grouse","Guanaco","Himalayan Tahr","Kangaroo","Koala","Numbat","Quoll","Raccoon dog","Tasmanian Devil","Thornbill","Turkey","Vole","Weasel","Wildcat","Wolf","Wombat","Woodchuck","Woodpecker"], - //list of farm animals comes from https://www.buzzle.com/articles/farm-animals-list.html - "farm" : ["Alpaca","Buffalo","Banteng","Cow","Cat","Chicken","Carp","Camel","Donkey","Dog","Duck","Emu","Goat","Gayal","Guinea","Goose","Horse","Honey","Llama","Pig","Pigeon","Rhea","Rabbit","Sheep","Silkworm","Turkey","Yak","Zebu"], - //list of pet animals comes from https://www.dogbreedinfo.com/pets/pet.htm - "pet" : ["Bearded Dragon","Birds","Burro","Cats","Chameleons","Chickens","Chinchillas","Chinese Water Dragon","Cows","Dogs","Donkey","Ducks","Ferrets","Fish","Geckos","Geese","Gerbils","Goats","Guinea Fowl","Guinea Pigs","Hamsters","Hedgehogs","Horses","Iguanas","Llamas","Lizards","Mice","Mule","Peafowl","Pigs and Hogs","Pigeons","Ponies","Pot Bellied Pig","Rabbits","Rats","Sheep","Skinks","Snakes","Stick Insects","Sugar Gliders","Tarantula","Turkeys","Turtles"], - //list of zoo animals comes from https://bronxzoo.com/animals - "zoo" : ["Aardvark","African Wild Dog","Aldabra Tortoise","American Alligator","American Bison","Amur Tiger","Anaconda","Andean Condor","Asian Elephant","Baby Doll Sheep","Bald Eagle","Barred Owl","Blue Iguana","Boer Goat","California Sea Lion","Caribbean Flamingo","Chinchilla","Collared Lemur","Coquerel's Sifaka","Cuban Amazon Parrot","Ebony Langur","Fennec Fox","Fossa","Gelada","Giant Anteater","Giraffe","Gorilla","Grizzly Bear","Henkel's Leaf-tailed Gecko","Indian Gharial","Indian Rhinoceros","King Cobra","King Vulture","Komodo Dragon","Linne's Two-toed Sloth","Lion","Little Penguin","Madagascar Tree Boa","Magellanic Penguin","Malayan Tapir","Malayan Tiger","Matschies Tree Kangaroo","Mini Donkey","Monarch Butterfly","Nile crocodile","North American Porcupine","Nubian Ibex","Okapi","Poison Dart Frog","Polar Bear","Pygmy Marmoset","Radiated Tortoise","Red Panda","Red Ruffed Lemur","Ring-tailed Lemur","Ring-tailed Mongoose","Rock Hyrax","Small Clawed Asian Otter","Snow Leopard","Snowy Owl","Southern White-faced Owl","Southern White Rhinocerous","Squirrel Monkey","Tufted Puffin","White Cheeked Gibbon","White-throated Bee Eater","Zebra"] - }, - primes: [ - // 1230 first primes, i.e. all primes up to the first one greater than 10000, inclusive. - 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,1171,1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,1277,1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367,1373,1381,1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,1487,1489,1493,1499,1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,1597,1601,1607,1609,1613,1619,1621,1627,1637,1657,1663,1667,1669,1693,1697,1699,1709,1721,1723,1733,1741,1747,1753,1759,1777,1783,1787,1789,1801,1811,1823,1831,1847,1861,1867,1871,1873,1877,1879,1889,1901,1907,1913,1931,1933,1949,1951,1973,1979,1987,1993,1997,1999,2003,2011,2017,2027,2029,2039,2053,2063,2069,2081,2083,2087,2089,2099,2111,2113,2129,2131,2137,2141,2143,2153,2161,2179,2203,2207,2213,2221,2237,2239,2243,2251,2267,2269,2273,2281,2287,2293,2297,2309,2311,2333,2339,2341,2347,2351,2357,2371,2377,2381,2383,2389,2393,2399,2411,2417,2423,2437,2441,2447,2459,2467,2473,2477,2503,2521,2531,2539,2543,2549,2551,2557,2579,2591,2593,2609,2617,2621,2633,2647,2657,2659,2663,2671,2677,2683,2687,2689,2693,2699,2707,2711,2713,2719,2729,2731,2741,2749,2753,2767,2777,2789,2791,2797,2801,2803,2819,2833,2837,2843,2851,2857,2861,2879,2887,2897,2903,2909,2917,2927,2939,2953,2957,2963,2969,2971,2999,3001,3011,3019,3023,3037,3041,3049,3061,3067,3079,3083,3089,3109,3119,3121,3137,3163,3167,3169,3181,3187,3191,3203,3209,3217,3221,3229,3251,3253,3257,3259,3271,3299,3301,3307,3313,3319,3323,3329,3331,3343,3347,3359,3361,3371,3373,3389,3391,3407,3413,3433,3449,3457,3461,3463,3467,3469,3491,3499,3511,3517,3527,3529,3533,3539,3541,3547,3557,3559,3571,3581,3583,3593,3607,3613,3617,3623,3631,3637,3643,3659,3671,3673,3677,3691,3697,3701,3709,3719,3727,3733,3739,3761,3767,3769,3779,3793,3797,3803,3821,3823,3833,3847,3851,3853,3863,3877,3881,3889,3907,3911,3917,3919,3923,3929,3931,3943,3947,3967,3989,4001,4003,4007,4013,4019,4021,4027,4049,4051,4057,4073,4079,4091,4093,4099,4111,4127,4129,4133,4139,4153,4157,4159,4177,4201,4211,4217,4219,4229,4231,4241,4243,4253,4259,4261,4271,4273,4283,4289,4297,4327,4337,4339,4349,4357,4363,4373,4391,4397,4409,4421,4423,4441,4447,4451,4457,4463,4481,4483,4493,4507,4513,4517,4519,4523,4547,4549,4561,4567,4583,4591,4597,4603,4621,4637,4639,4643,4649,4651,4657,4663,4673,4679,4691,4703,4721,4723,4729,4733,4751,4759,4783,4787,4789,4793,4799,4801,4813,4817,4831,4861,4871,4877,4889,4903,4909,4919,4931,4933,4937,4943,4951,4957,4967,4969,4973,4987,4993,4999,5003,5009,5011,5021,5023,5039,5051,5059,5077,5081,5087,5099,5101,5107,5113,5119,5147,5153,5167,5171,5179,5189,5197,5209,5227,5231,5233,5237,5261,5273,5279,5281,5297,5303,5309,5323,5333,5347,5351,5381,5387,5393,5399,5407,5413,5417,5419,5431,5437,5441,5443,5449,5471,5477,5479,5483,5501,5503,5507,5519,5521,5527,5531,5557,5563,5569,5573,5581,5591,5623,5639,5641,5647,5651,5653,5657,5659,5669,5683,5689,5693,5701,5711,5717,5737,5741,5743,5749,5779,5783,5791,5801,5807,5813,5821,5827,5839,5843,5849,5851,5857,5861,5867,5869,5879,5881,5897,5903,5923,5927,5939,5953,5981,5987,6007,6011,6029,6037,6043,6047,6053,6067,6073,6079,6089,6091,6101,6113,6121,6131,6133,6143,6151,6163,6173,6197,6199,6203,6211,6217,6221,6229,6247,6257,6263,6269,6271,6277,6287,6299,6301,6311,6317,6323,6329,6337,6343,6353,6359,6361,6367,6373,6379,6389,6397,6421,6427,6449,6451,6469,6473,6481,6491,6521,6529,6547,6551,6553,6563,6569,6571,6577,6581,6599,6607,6619,6637,6653,6659,6661,6673,6679,6689,6691,6701,6703,6709,6719,6733,6737,6761,6763,6779,6781,6791,6793,6803,6823,6827,6829,6833,6841,6857,6863,6869,6871,6883,6899,6907,6911,6917,6947,6949,6959,6961,6967,6971,6977,6983,6991,6997,7001,7013,7019,7027,7039,7043,7057,7069,7079,7103,7109,7121,7127,7129,7151,7159,7177,7187,7193,7207,7211,7213,7219,7229,7237,7243,7247,7253,7283,7297,7307,7309,7321,7331,7333,7349,7351,7369,7393,7411,7417,7433,7451,7457,7459,7477,7481,7487,7489,7499,7507,7517,7523,7529,7537,7541,7547,7549,7559,7561,7573,7577,7583,7589,7591,7603,7607,7621,7639,7643,7649,7669,7673,7681,7687,7691,7699,7703,7717,7723,7727,7741,7753,7757,7759,7789,7793,7817,7823,7829,7841,7853,7867,7873,7877,7879,7883,7901,7907,7919,7927,7933,7937,7949,7951,7963,7993,8009,8011,8017,8039,8053,8059,8069,8081,8087,8089,8093,8101,8111,8117,8123,8147,8161,8167,8171,8179,8191,8209,8219,8221,8231,8233,8237,8243,8263,8269,8273,8287,8291,8293,8297,8311,8317,8329,8353,8363,8369,8377,8387,8389,8419,8423,8429,8431,8443,8447,8461,8467,8501,8513,8521,8527,8537,8539,8543,8563,8573,8581,8597,8599,8609,8623,8627,8629,8641,8647,8663,8669,8677,8681,8689,8693,8699,8707,8713,8719,8731,8737,8741,8747,8753,8761,8779,8783,8803,8807,8819,8821,8831,8837,8839,8849,8861,8863,8867,8887,8893,8923,8929,8933,8941,8951,8963,8969,8971,8999,9001,9007,9011,9013,9029,9041,9043,9049,9059,9067,9091,9103,9109,9127,9133,9137,9151,9157,9161,9173,9181,9187,9199,9203,9209,9221,9227,9239,9241,9257,9277,9281,9283,9293,9311,9319,9323,9337,9341,9343,9349,9371,9377,9391,9397,9403,9413,9419,9421,9431,9433,9437,9439,9461,9463,9467,9473,9479,9491,9497,9511,9521,9533,9539,9547,9551,9587,9601,9613,9619,9623,9629,9631,9643,9649,9661,9677,9679,9689,9697,9719,9721,9733,9739,9743,9749,9767,9769,9781,9787,9791,9803,9811,9817,9829,9833,9839,9851,9857,9859,9871,9883,9887,9901,9907,9923,9929,9931,9941,9949,9967,9973,10007 - ], - emotions: [ - "love", - "joy", - "surprise", - "anger", - "sadness", - "fear" - ], - music_genres: { - 'general': [ - 'Rock', - 'Pop', - 'Hip-Hop', - 'Jazz', - 'Classical', - 'Electronic', - 'Country', - 'R&B', - 'Reggae', - 'Blues', - 'Metal', - 'Folk', - 'Alternative', - 'Punk', - 'Disco', - 'Funk', - 'Techno', - 'Indie', - 'Gospel', - 'Dance', - 'Children\'s', - 'World' - ], - 'alternative': [ - 'Art Punk', - 'Alternative Rock', - 'Britpunk', - 'College Rock', - 'Crossover Thrash', - 'Crust Punk', - 'Emo / Emocore', - 'Experimental Rock', - 'Folk Punk', - 'Goth / Gothic Rock', - 'Grunge', - 'Hardcore Punk', - 'Hard Rock', - 'Indie Rock', - 'Lo-fi', - 'Musique Concrète', - 'New Wave', - 'Progressive Rock', - 'Punk', - 'Shoegaze', - 'Steampunk', - ], 'blues': [ - 'Acoustic Blues', - 'African Blues', - 'Blues Rock', - 'Blues Shouter', - 'British Blues', - 'Canadian Blues', - 'Chicago Blues', - 'Classic Blues', - 'Classic Female Blues', - 'Contemporary Blues', - 'Country Blues', - 'Dark Blues', - 'Delta Blues', - 'Detroit Blues', - 'Doom Blues', - 'Electric Blues', - 'Folk Blues', - 'Gospel Blues', - 'Harmonica Blues', - 'Hill Country Blues', - 'Hokum Blues', - 'Jazz Blues', - 'Jump Blues', - 'Kansas City Blues', - 'Louisiana Blues', - 'Memphis Blues', - 'Modern Blues', - 'New Orlean Blues', - 'NY Blues', - 'Piano Blues', - 'Piedmont Blues', - 'Punk Blues', - 'Ragtime Blues', - 'Rhythm Blues', - 'Soul Blues', - 'St.Louis Blues', - 'Soul Blues', - 'Swamp Blues', - 'Texas Blues', - 'Urban Blues', - 'Vandeville', - 'West Coast Blues', - ], 'children\'s': [ - 'Lullabies', - 'Sing - Along', - 'Stories' - ], 'classical': [ - 'Avant-Garde', - 'Ballet', - 'Baroque', - 'Cantata', - 'Chamber Music', - 'String Quartet', - 'Chant', - 'Choral', - 'Classical Crossover', - 'Concerto', - 'Concerto Grosso', - 'Contemporary Classical', - 'Early Music', - 'Expressionist', - 'High Classical', - 'Impressionist', - 'Mass Requiem', - 'Medieval', - 'Minimalism', - 'Modern Composition', - 'Modern Classical', - 'Opera', - 'Oratorio', - 'Orchestral', - 'Organum', - 'Renaissance', - 'Romantic (early period)', - 'Romantic (later period)', - 'Sonata', - 'Symphonic', - 'Symphony', - 'Twelve-tone', - 'Wedding Music' - ], 'country': [ - 'Alternative Country', - 'Americana', - 'Australian Country', - 'Bakersfield Sound', - 'Bluegrass', - 'Blues Country', - 'Cajun Fiddle Tunes', - 'Christian Country', - 'Classic Country', - 'Close Harmony', - 'Contemporary Bluegrass', - 'Contemporary Country', - 'Country Gospel', - 'Country Pop', - 'Country Rap', - 'Country Rock', - 'Country Soul', - 'Cowboy / Western', - 'Cowpunk', - 'Dansband', - 'Honky Tonk', - 'Franco-Country', - 'Gulf and Western', - 'Hellbilly Music', - 'Honky Tonk', - 'Instrumental Country', - 'Lubbock Sound', - 'Nashville Sound', - 'Neotraditional Country', - 'Outlaw Country', - 'Progressive', - 'Psychobilly / Punkabilly', - 'Red Dirt', - 'Sertanejo', - 'Texas County', - 'Traditional Bluegrass', - 'Traditional Country', - 'Truck-Driving Country', - 'Urban Cowboy', - 'Western Swing' - ], 'dance': [ - 'Club / Club Dance', - 'Breakcore', - 'Breakbeat / Breakstep', - 'Chillstep', - 'Deep House', - 'Dubstep', - 'Dancehall', - 'Electro House', - 'Electroswing', - 'Exercise', - 'Future Garage', - 'Garage', - 'Glitch Hop', - 'Glitch Pop', - 'Grime', - 'Hardcore', - 'Hard Dance', - 'Hi-NRG / Eurodance', - 'Horrorcore', - 'House', - 'Jackin House', - 'Jungle / Drum n bass', - 'Liquid Dub', - 'Regstep', - 'Speedcore', - 'Techno', - 'Trance', - 'Trap' - ], electronic: [ - '2-Step', - '8bit', - 'Ambient', - 'Asian Underground', - 'Bassline', - 'Chillwave', - 'Chiptune', - 'Crunk', - 'Downtempo', - 'Drum & Bass', - 'Hard Step', - 'Electro', - 'Electro-swing', - 'Electroacoustic', - 'Electronica', - 'Electronic Rock', - 'Eurodance', - 'Hardstyle', - 'Hi-Nrg', - 'IDM/Experimental', - 'Industrial', - 'Trip Hop', - 'Vaporwave', - 'UK Garage', - 'House', - 'Dubstep', - 'Deep House', - 'EDM', - 'Future Bass', - 'Psychedelic trance' - ], 'jazz' : [ - 'Acid Jazz', - 'Afro-Cuban Jazz', - 'Avant-Garde Jazz', - 'Bebop', - 'Big Band', - 'Blue Note', - 'British Dance Band (Jazz)', - 'Cape Jazz', - 'Chamber Jazz', - 'Contemporary Jazz', - 'Continental Jazz', - 'Cool Jazz', - 'Crossover Jazz', - 'Dark Jazz', - 'Dixieland', - 'Early Jazz', - 'Electro Swing (Jazz)', - 'Ethio-jazz', - 'Ethno-Jazz', - 'European Free Jazz', - 'Free Funk (Avant-Garde / Funk Jazz)', - 'Free Jazz', - 'Fusion', - 'Gypsy Jazz', - 'Hard Bop', - 'Indo Jazz', - 'Jazz Blues', - 'Jazz-Funk (see Free Funk)', - 'Jazz-Fusion', - 'Jazz Rap', - 'Jazz Rock', - 'Kansas City Jazz', - 'Latin Jazz', - 'M-Base Jazz', - 'Mainstream Jazz', - 'Modal Jazz', - 'Neo-Bop', - 'Neo-Swing', - 'Nu Jazz', - 'Orchestral Jazz', - 'Post-Bop', - 'Punk Jazz', - 'Ragtime', - 'Ska Jazz', - 'Skiffle (also Folk)', - 'Smooth Jazz', - 'Soul Jazz', - 'Swing Jazz', - 'Straight-Ahead Jazz', - 'Trad Jazz', - 'Third Stream', - 'Jazz-Funk', - 'Free Jazz', - 'West Coast Jazz' - ], 'metal': [ - 'Heavy Metal', - 'Speed Metal', - 'Thrash Metal', - 'Power Metal', - 'Death Metal', - 'Black Metal', - 'Pagan Metal', - 'Viking Metal', - 'Folk Metal', - 'Symphonic Metal', - 'Gothic Metal', - 'Glam Metal', - 'Hair Metal', - 'Doom Metal', - 'Groove Metal', - 'Industrial Metal', - 'Modern Metal', - 'Neoclassical Metal', - 'New Wave Of British Heavy Metal', - 'Post Metal', - 'Progressive Metal', - 'Avantgarde Metal', - 'Sludge', - 'Djent', - 'Drone', - 'Kawaii Metal', - 'Pirate Metal', - 'Nu Metal', - 'Neue Deutsche Härte', - 'Math Metal', - 'Crossover', - 'Grindcore', - 'Hardcore', - 'Metalcore', - 'Deathcore', - 'Post Hardcore', - 'Mathcore' - ], 'folk': [ - 'American Folk Revival', - 'Anti - Folk', - 'British Folk Revival', - 'Contemporary Folk', - 'Filk Music', - 'Freak Folk', - 'Indie Folk', - 'Industrial Folk', - 'Neofolk', - 'Progressive Folk', - 'Psychedelic Folk', - 'Sung Poetry', - 'Techno - Folk', - 'Folk Rock', - 'Old-time Music', - 'Bluegrass', - 'Appalachian', - 'Roots Revival', - 'Celtic', - 'Indie Folk' - ], 'pop': [ - 'Adult Contemporary', - 'Arab Pop', - 'Baroque', - 'Britpop', - 'Bubblegum Pop', - 'Chamber Pop', - 'Chanson', - 'Christian Pop', - 'Classical Crossover', - 'Europop', - 'Austropop', - 'Balkan Pop', - 'French Pop', - 'Korean Pop', - 'Japanese Pop', - 'Chinese Pop', - 'Latin Pop', - 'Laïkó', - 'Nederpop', - 'Russian Pop', - 'Dance Pop', - 'Dream Pop', - 'Electro Pop', - 'Iranian Pop', - 'Jangle Pop', - 'Latin Ballad', - 'Levenslied', - 'Louisiana Swamp Pop', - 'Mexican Pop', - 'Motorpop', - 'New Romanticism', - 'Orchestral Pop', - 'Pop Rap', - 'Popera', - 'Pop / Rock', - 'Pop Punk', - 'Power Pop', - 'Psychedelic Pop', - 'Russian Pop', - 'Schlager', - 'Soft Rock', - 'Sophisti - Pop', - 'Space Age Pop', - 'Sunshine Pop', - 'Surf Pop', - 'Synthpop', - 'Teen Pop', - 'Traditional Pop Music', - 'Turkish Pop', - 'Vispop', - 'Wonky Pop' - ], 'r&b': [ - '(Carolina) Beach Music', - 'Contemporary R & B', - 'Disco', - 'Doo Wop', - 'Funk', - 'Modern Soul', - 'Motown', - 'Neo - Soul', - 'Northern Soul', - 'Psychedelic Soul', - 'Quiet Storm', - 'Soul', - 'Soul Blues', - 'Southern Soul' - ], 'reggae': [ - '2 - Tone', - 'Dub', - 'Roots Reggae', - 'Reggae Fusion', - 'Reggae en Español', - 'Spanish Reggae', - 'Reggae 110', - 'Reggae Bultrón', - 'Romantic Flow', - 'Lovers Rock', - 'Raggamuffin', - 'Ragga', - 'Dancehall', - 'Ska', - ], 'rock': [ - 'Acid Rock', - 'Adult - Oriented Rock', - 'Afro Punk', - 'Adult Alternative', - 'Alternative Rock', - 'American Traditional Rock', - 'Anatolian Rock', - 'Arena Rock', - 'Art Rock', - 'Blues - Rock', - 'British Invasion', - 'Cock Rock', - 'Death Metal / Black Metal', - 'Doom Metal', - 'Glam Rock', - 'Gothic Metal', - 'Grind Core', - 'Hair Metal', - 'Hard Rock', - 'Math Metal', - 'Math Rock', - 'Metal', - 'Metal Core', - 'Noise Rock', - 'Jam Bands', - 'Post Punk', - 'Post Rock', - 'Prog - Rock / Art Rock', - 'Progressive Metal', - 'Psychedelic', - 'Rock & Roll', - 'Rockabilly', - 'Roots Rock', - 'Singer / Songwriter', - 'Southern Rock', - 'Spazzcore', - 'Stoner Metal', - 'Surf', - 'Technical Death Metal', - 'Tex - Mex', - 'Thrash Metal', - 'Time Lord Rock(Trock)', - 'Trip - hop', - 'Yacht Rock', - 'School House Rock' - ], 'hip-hop': [ - 'Alternative Rap', - 'Avant - Garde', - 'Bounce', - 'Chap Hop', - 'Christian Hip Hop', - 'Conscious Hip Hop', - 'Country - Rap', - 'Grunk', - 'Crunkcore', - 'Cumbia Rap', - 'Dirty South', - 'East Coast', - 'Brick City Club', - 'Hardcore Hip Hop', - 'Mafioso Rap', - 'New Jersey Hip Hop', - 'Freestyle Rap', - 'G - Funk', - 'Gangsta Rap', - 'Golden Age', - 'Grime', - 'Hardcore Rap', - 'Hip - Hop', - 'Hip Pop', - 'Horrorcore', - 'Hyphy', - 'Industrial Hip Hop', - 'Instrumental Hip Hop', - 'Jazz Rap', - 'Latin Rap', - 'Low Bap', - 'Lyrical Hip Hop', - 'Merenrap', - 'Midwest Hip Hop', - 'Chicago Hip Hop', - 'Detroit Hip Hop', - 'Horrorcore', - 'St.Louis Hip Hop', - 'Twin Cities Hip Hop', - 'Motswako', - 'Nerdcore', - 'New Jack Swing', - 'New School Hip Hop', - 'Old School Rap', - 'Rap', - 'Trap', - 'Turntablism', - 'Underground Rap', - 'West Coast Rap', - 'East Coast Rap', - 'Trap', - 'UK Grime', - 'Hyphy', - 'Emo-rap', - 'Cloud rap', - 'G-funk', - 'Boom Bap', - 'Mumble', - 'Drill', - 'UK Drill', - 'Soundcloud Rap', - 'Lo-fi' - ], 'punk': [ - 'Afro-punk', - 'Anarcho punk', - 'Art punk', - 'Christian punk', - 'Crust punk', - 'Deathrock', - 'Egg punk', - 'Garage punk', - 'Glam punk', - 'Hardcore punk', - 'Horror punk', - 'Incelcore/e-punk', - 'Oi!', - 'Peace punk', - 'Punk pathetique', - 'Queercore', - 'Riot Grrrl', - 'Skate punk', - 'Street punk', - 'Taqwacore', - 'Trallpunk' - ], 'disco': [ - 'Nu-disco', - 'Disco-funk', - 'Hi-NRG', - 'Italo Disco', - 'Eurodisco', - 'Boogie', - 'Space Disco', - 'Post-disco', - 'Electro Disco', - 'Disco House', - 'Disco Pop', - 'Soulful House' - ], 'funk': [ - 'Funk Rock', - 'P-Funk (Parliament-Funkadelic)', - 'Psychedelic Funk', - 'Funk Metal', - 'Electro-Funk', - 'Go-go', - 'Boogie-Funk', - 'Jazz-Funk', - 'Soul-Funk', - 'Funky Disco', - 'Nu-Funk', - 'Afrobeat', - 'Latin Funk', - 'G-Funk', - 'Acid Jazz', - 'Funktronica', - 'Folk-Funk', - 'Space Funk', - 'Ambient Funk', - 'Hard Funk', - 'Fusion Funk' - ], 'techno': [ - 'Acid Techno', - 'Ambient Techno', - 'Detroit Techno', - 'Dub Techno', - 'Minimal Techno', - 'Industrial Techno', - 'Hard Techno', - 'Trance', - 'Progressive Techno', - 'Tech House', - 'Electronica', - 'Breakbeat Techno', - 'Electro Techno', - 'Melodic Techno', - 'Experimental Techno', - 'Dark Techno', - 'Ebm', - 'Hypnotic Techno', - 'Psychedelic Techno', - 'Rave Techno', - 'Techno-Pop' - ], 'indie': [ - 'Indie Rock', - 'Indie Pop', - 'Indie Folk', - 'Indie Electronic', - 'Indie Punk', - 'Indie Hip-Hop', - 'Dream Pop', - 'Shoegaze', - 'Lo-fi', - 'Chillwave', - 'Freak Folk', - 'Noise Pop', - 'Math Rock', - 'Post-Punk', - 'Garage Rock', - 'Experimental Indie', - 'Surf Rock', - 'Alternative Country', - 'Indie Soul', - 'Art Rock', - 'Indie R&B', - 'Indietronica', - 'Emo', - 'Post-Rock', - 'Indie Pop-Rock', - 'Indie Synthpop', - 'Noise Rock', - 'Psych Folk', - 'Indie Blues' - ], 'gospel': [ - 'Traditional Gospel', - 'Contemporary Gospel', - 'Southern Gospel', - 'Black Gospel', - 'Urban Contemporary Gospel', - 'Gospel Blues', - 'Bluegrass Gospel', - 'Country Gospel', - 'Praise and Worship', - 'Christian Hip-Hop', - 'Gospel Jazz', - 'Reggae Gospel', - 'African Gospel', - 'Latin Gospel', - 'R&B Gospel', - 'Gospel Choir', - 'Acappella Gospel', - 'Instrumental Gospel', - 'Gospel Rap' - ], 'world': [ - 'African', - 'Arabic', - 'Asian', - 'Caribbean', - 'Celtic', - 'European', - 'Latin American', - 'Middle Eastern', - 'Native American', - 'Polynesian', - 'Reggae', - 'Ska', - 'Salsa', - 'Flamenco', - 'Bossa Nova', - 'Tango', - 'Fado', - 'Klezmer', - 'Balkan', - 'Afrobeat', - 'Mongolian Throat Singing', - 'Indian Classical', - 'Gamelan', - 'Sufi Music', - 'Zydeco', - 'Kora Music', - 'Andean Music', - 'Irish Traditional', - 'Gypsy Jazz', - 'Bollywood', - 'Bhangra', - 'Jawaiian', - 'Hawaiian Slack Key Guitar', - 'Calypso', - 'Cuban Son', - 'Taiko Drumming', - 'African Highlife', - 'Merengue', - 'Tuvan Throat Singing' - ] - }, - - // Data sourced from https://unicode.org/emoji/charts/full-emoji-list.html - emojis: { - "smileys_and_emotion": [ - "0x1f600", - "0x1f603", - "0x1f604", - "0x1f601", - "0x1f606", - "0x1f605", - "0x1f923", - "0x1f602", - "0x1f642", - "0x1f643", - "0x1fae0", - "0x1f609", - "0x1f60a", - "0x1f607", - "0x1f970", - "0x1f60d", - "0x1f929", - "0x1f618", - "0x1f617", - "0x263a", - "0x1f61a", - "0x1f619", - "0x1f972", - "0x1f60b", - "0x1f61b", - "0x1f61c", - "0x1f92a", - "0x1f61d", - "0x1f911", - "0x1f917", - "0x1f92d", - "0x1fae2", - "0x1fae3", - "0x1f92b", - "0x1f914", - "0x1fae1", - "0x1f910", - "0x1f928", - "0x1f610", - "0x1f611", - "0x1f636", - "0x1fae5", - "0x1f636", - "0x200d", - "0x1f32b", - "0xfe0f", - "0x1f60f", - "0x1f612", - "0x1f644", - "0x1f62c", - "0x1f62e", - "0x200d", - "0x1f4a8", - "0x1f925", - "0x1fae8", - "0x1f642", - "0x200d", - "0x2194", - "0xfe0f", - "0x1f642", - "0x200d", - "0x2195", - "0xfe0f", - "0x1f60c", - "0x1f614", - "0x1f62a", - "0x1f924", - "0x1f634", - "0x1f637", - "0x1f912", - "0x1f915", - "0x1f922", - "0x1f92e", - "0x1f927", - "0x1f975", - "0x1f976", - "0x1f974", - "0x1f635", - "0x1f635", - "0x200d", - "0x1f4ab", - "0x1f92f", - "0x1f920", - "0x1f973", - "0x1f978", - "0x1f60e", - "0x1f913", - "0x1f9d0", - "0x1f615", - "0x1fae4", - "0x1f61f", - "0x1f641", - "0x2639", - "0x1f62e", - "0x1f62f", - "0x1f632", - "0x1f633", - "0x1f97a", - "0x1f979", - "0x1f626", - "0x1f627", - "0x1f628", - "0x1f630", - "0x1f625", - "0x1f622", - "0x1f62d", - "0x1f631", - "0x1f616", - "0x1f623", - "0x1f61e", - "0x1f613", - "0x1f629", - "0x1f62b", - "0x1f971", - "0x1f624", - "0x1f621", - "0x1f620", - "0x1f92c", - "0x1f608", - "0x1f47f", - "0x1f480", - "0x2620", - "0x1f4a9", - "0x1f921", - "0x1f479", - "0x1f47a", - "0x1f47b", - "0x1f47d", - "0x1f47e", - "0x1f916", - "0x1f63a", - "0x1f638", - "0x1f639", - "0x1f63b", - "0x1f63c", - "0x1f63d", - "0x1f640", - "0x1f63f", - "0x1f63e", - "0x1f648", - "0x1f649", - "0x1f64a", - "0x1f48c", - "0x1f498", - "0x1f49d", - "0x1f496", - "0x1f497", - "0x1f493", - "0x1f49e", - "0x1f495", - "0x1f49f", - "0x2763", - "0x1f494", - "0x2764", - "0xfe0f", - "0x200d", - "0x1f525", - "0x2764", - "0xfe0f", - "0x200d", - "0x1fa79", - "0x2764", - "0x1fa77", - "0x1f9e1", - "0x1f49b", - "0x1f49a", - "0x1f499", - "0x1fa75", - "0x1f49c", - "0x1f90e", - "0x1f5a4", - "0x1fa76", - "0x1f90d", - "0x1f48b", - "0x1f4af", - "0x1f4a2", - "0x1f4a5", - "0x1f4ab", - "0x1f4a6", - "0x1f4a8", - "0x1f573", - "0x1f4ac", - "0x1f441", - "0xfe0f", - "0x200d", - "0x1f5e8", - "0xfe0f", - "0x1f5e8", - "0x1f5ef", - "0x1f4ad", - "0x1f4a4" - ], - "people_and_body": [ - "0x1f44b", - "0x1f91a", - "0x1f590", - "0x270b", - "0x1f596", - "0x1faf1", - "0x1faf2", - "0x1faf3", - "0x1faf4", - "0x1faf7", - "0x1faf8", - "0x1f44c", - "0x1f90c", - "0x1f90f", - "0x270c", - "0x1f91e", - "0x1faf0", - "0x1f91f", - "0x1f918", - "0x1f919", - "0x1f448", - "0x1f449", - "0x1f446", - "0x1f595", - "0x1f447", - "0x261d", - "0x1faf5", - "0x1f44d", - "0x1f44e", - "0x270a", - "0x1f44a", - "0x1f91b", - "0x1f91c", - "0x1f44f", - "0x1f64c", - "0x1faf6", - "0x1f450", - "0x1f932", - "0x1f91d", - "0x1f64f", - "0x270d", - "0x1f485", - "0x1f933", - "0x1f4aa", - "0x1f9be", - "0x1f9bf", - "0x1f9b5", - "0x1f9b6", - "0x1f442", - "0x1f9bb", - "0x1f443", - "0x1f9e0", - "0x1fac0", - "0x1fac1", - "0x1f9b7", - "0x1f9b4", - "0x1f440", - "0x1f441", - "0x1f445", - "0x1f444", - "0x1fae6", - "0x1f476", - "0x1f9d2", - "0x1f466", - "0x1f467", - "0x1f9d1", - "0x1f471", - "0x1f468", - "0x1f9d4", - "0x1f9d4", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9d4", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f468", - "0x200d", - "0x1f9b0", - "0x1f468", - "0x200d", - "0x1f9b1", - "0x1f468", - "0x200d", - "0x1f9b3", - "0x1f468", - "0x200d", - "0x1f9b2", - "0x1f469", - "0x1f469", - "0x200d", - "0x1f9b0", - "0x1f9d1", - "0x200d", - "0x1f9b0", - "0x1f469", - "0x200d", - "0x1f9b1", - "0x1f9d1", - "0x200d", - "0x1f9b1", - "0x1f469", - "0x200d", - "0x1f9b3", - "0x1f9d1", - "0x200d", - "0x1f9b3", - "0x1f469", - "0x200d", - "0x1f9b2", - "0x1f9d1", - "0x200d", - "0x1f9b2", - "0x1f471", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f471", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9d3", - "0x1f474", - "0x1f475", - "0x1f64d", - "0x1f64d", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f64d", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f64e", - "0x1f64e", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f64e", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f645", - "0x1f645", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f645", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f646", - "0x1f646", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f646", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f481", - "0x1f481", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f481", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f64b", - "0x1f64b", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f64b", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9cf", - "0x1f9cf", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9cf", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f647", - "0x1f647", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f647", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f926", - "0x1f926", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f926", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f937", - "0x1f937", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f937", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9d1", - "0x200d", - "0x2695", - "0xfe0f", - "0x1f468", - "0x200d", - "0x2695", - "0xfe0f", - "0x1f469", - "0x200d", - "0x2695", - "0xfe0f", - "0x1f9d1", - "0x200d", - "0x1f393", - "0x1f468", - "0x200d", - "0x1f393", - "0x1f469", - "0x200d", - "0x1f393", - "0x1f9d1", - "0x200d", - "0x1f3eb", - "0x1f468", - "0x200d", - "0x1f3eb", - "0x1f469", - "0x200d", - "0x1f3eb", - "0x1f9d1", - "0x200d", - "0x2696", - "0xfe0f", - "0x1f468", - "0x200d", - "0x2696", - "0xfe0f", - "0x1f469", - "0x200d", - "0x2696", - "0xfe0f", - "0x1f9d1", - "0x200d", - "0x1f33e", - "0x1f468", - "0x200d", - "0x1f33e", - "0x1f469", - "0x200d", - "0x1f33e", - "0x1f9d1", - "0x200d", - "0x1f373", - "0x1f468", - "0x200d", - "0x1f373", - "0x1f469", - "0x200d", - "0x1f373", - "0x1f9d1", - "0x200d", - "0x1f527", - "0x1f468", - "0x200d", - "0x1f527", - "0x1f469", - "0x200d", - "0x1f527", - "0x1f9d1", - "0x200d", - "0x1f3ed", - "0x1f468", - "0x200d", - "0x1f3ed", - "0x1f469", - "0x200d", - "0x1f3ed", - "0x1f9d1", - "0x200d", - "0x1f4bc", - "0x1f468", - "0x200d", - "0x1f4bc", - "0x1f469", - "0x200d", - "0x1f4bc", - "0x1f9d1", - "0x200d", - "0x1f52c", - "0x1f468", - "0x200d", - "0x1f52c", - "0x1f469", - "0x200d", - "0x1f52c", - "0x1f9d1", - "0x200d", - "0x1f4bb", - "0x1f468", - "0x200d", - "0x1f4bb", - "0x1f469", - "0x200d", - "0x1f4bb", - "0x1f9d1", - "0x200d", - "0x1f3a4", - "0x1f468", - "0x200d", - "0x1f3a4", - "0x1f469", - "0x200d", - "0x1f3a4", - "0x1f9d1", - "0x200d", - "0x1f3a8", - "0x1f468", - "0x200d", - "0x1f3a8", - "0x1f469", - "0x200d", - "0x1f3a8", - "0x1f9d1", - "0x200d", - "0x2708", - "0xfe0f", - "0x1f468", - "0x200d", - "0x2708", - "0xfe0f", - "0x1f469", - "0x200d", - "0x2708", - "0xfe0f", - "0x1f9d1", - "0x200d", - "0x1f680", - "0x1f468", - "0x200d", - "0x1f680", - "0x1f469", - "0x200d", - "0x1f680", - "0x1f9d1", - "0x200d", - "0x1f692", - "0x1f468", - "0x200d", - "0x1f692", - "0x1f469", - "0x200d", - "0x1f692", - "0x1f46e", - "0x1f46e", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f46e", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f575", - "0x1f575", - "0xfe0f", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f575", - "0xfe0f", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f482", - "0x1f482", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f482", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f977", - "0x1f477", - "0x1f477", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f477", - "0x200d", - "0x2640", - "0xfe0f", - "0x1fac5", - "0x1f934", - "0x1f478", - "0x1f473", - "0x1f473", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f473", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f472", - "0x1f9d5", - "0x1f935", - "0x1f935", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f935", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f470", - "0x1f470", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f470", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f930", - "0x1fac3", - "0x1fac4", - "0x1f931", - "0x1f469", - "0x200d", - "0x1f37c", - "0x1f468", - "0x200d", - "0x1f37c", - "0x1f9d1", - "0x200d", - "0x1f37c", - "0x1f47c", - "0x1f385", - "0x1f936", - "0x1f9d1", - "0x200d", - "0x1f384", - "0x1f9b8", - "0x1f9b8", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9b8", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9b9", - "0x1f9b9", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9b9", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9d9", - "0x1f9d9", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9d9", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9da", - "0x1f9da", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9da", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9db", - "0x1f9db", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9db", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9dc", - "0x1f9dc", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9dc", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9dd", - "0x1f9dd", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9dd", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9de", - "0x1f9de", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9de", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9df", - "0x1f9df", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9df", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9cc", - "0x1f486", - "0x1f486", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f486", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f487", - "0x1f487", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f487", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f6b6", - "0x1f6b6", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f6b6", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f6b6", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f6b6", - "0x200d", - "0x2640", - "0xfe0f", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f6b6", - "0x200d", - "0x2642", - "0xfe0f", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f9cd", - "0x1f9cd", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9cd", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9ce", - "0x1f9ce", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9ce", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9ce", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f9ce", - "0x200d", - "0x2640", - "0xfe0f", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f9ce", - "0x200d", - "0x2642", - "0xfe0f", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f9d1", - "0x200d", - "0x1f9af", - "0x1f9d1", - "0x200d", - "0x1f9af", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f468", - "0x200d", - "0x1f9af", - "0x1f468", - "0x200d", - "0x1f9af", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f469", - "0x200d", - "0x1f9af", - "0x1f469", - "0x200d", - "0x1f9af", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f9d1", - "0x200d", - "0x1f9bc", - "0x1f9d1", - "0x200d", - "0x1f9bc", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f468", - "0x200d", - "0x1f9bc", - "0x1f468", - "0x200d", - "0x1f9bc", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f469", - "0x200d", - "0x1f9bc", - "0x1f469", - "0x200d", - "0x1f9bc", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f9d1", - "0x200d", - "0x1f9bd", - "0x1f9d1", - "0x200d", - "0x1f9bd", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f468", - "0x200d", - "0x1f9bd", - "0x1f468", - "0x200d", - "0x1f9bd", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f469", - "0x200d", - "0x1f9bd", - "0x1f469", - "0x200d", - "0x1f9bd", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f3c3", - "0x1f3c3", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f3c3", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f3c3", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f3c3", - "0x200d", - "0x2640", - "0xfe0f", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f3c3", - "0x200d", - "0x2642", - "0xfe0f", - "0x200d", - "0x27a1", - "0xfe0f", - "0x1f483", - "0x1f57a", - "0x1f574", - "0x1f46f", - "0x1f46f", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f46f", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9d6", - "0x1f9d6", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9d6", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9d7", - "0x1f9d7", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9d7", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f93a", - "0x1f3c7", - "0x26f7", - "0x1f3c2", - "0x1f3cc", - "0x1f3cc", - "0xfe0f", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f3cc", - "0xfe0f", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f3c4", - "0x1f3c4", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f3c4", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f6a3", - "0x1f6a3", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f6a3", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f3ca", - "0x1f3ca", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f3ca", - "0x200d", - "0x2640", - "0xfe0f", - "0x26f9", - "0x26f9", - "0xfe0f", - "0x200d", - "0x2642", - "0xfe0f", - "0x26f9", - "0xfe0f", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f3cb", - "0x1f3cb", - "0xfe0f", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f3cb", - "0xfe0f", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f6b4", - "0x1f6b4", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f6b4", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f6b5", - "0x1f6b5", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f6b5", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f938", - "0x1f938", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f938", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f93c", - "0x1f93c", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f93c", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f93d", - "0x1f93d", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f93d", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f93e", - "0x1f93e", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f93e", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f939", - "0x1f939", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f939", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f9d8", - "0x1f9d8", - "0x200d", - "0x2642", - "0xfe0f", - "0x1f9d8", - "0x200d", - "0x2640", - "0xfe0f", - "0x1f6c0", - "0x1f6cc", - "0x1f9d1", - "0x200d", - "0x1f91d", - "0x200d", - "0x1f9d1", - "0x1f46d", - "0x1f46b", - "0x1f46c", - "0x1f48f", - "0x1f469", - "0x200d", - "0x2764", - "0xfe0f", - "0x200d", - "0x1f48b", - "0x200d", - "0x1f468", - "0x1f468", - "0x200d", - "0x2764", - "0xfe0f", - "0x200d", - "0x1f48b", - "0x200d", - "0x1f468", - "0x1f469", - "0x200d", - "0x2764", - "0xfe0f", - "0x200d", - "0x1f48b", - "0x200d", - "0x1f469", - "0x1f491", - "0x1f469", - "0x200d", - "0x2764", - "0xfe0f", - "0x200d", - "0x1f468", - "0x1f468", - "0x200d", - "0x2764", - "0xfe0f", - "0x200d", - "0x1f468", - "0x1f469", - "0x200d", - "0x2764", - "0xfe0f", - "0x200d", - "0x1f469", - "0x1f468", - "0x200d", - "0x1f469", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f469", - "0x200d", - "0x1f467", - "0x1f468", - "0x200d", - "0x1f469", - "0x200d", - "0x1f467", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f469", - "0x200d", - "0x1f466", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f469", - "0x200d", - "0x1f467", - "0x200d", - "0x1f467", - "0x1f468", - "0x200d", - "0x1f468", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f468", - "0x200d", - "0x1f467", - "0x1f468", - "0x200d", - "0x1f468", - "0x200d", - "0x1f467", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f468", - "0x200d", - "0x1f466", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f468", - "0x200d", - "0x1f467", - "0x200d", - "0x1f467", - "0x1f469", - "0x200d", - "0x1f469", - "0x200d", - "0x1f466", - "0x1f469", - "0x200d", - "0x1f469", - "0x200d", - "0x1f467", - "0x1f469", - "0x200d", - "0x1f469", - "0x200d", - "0x1f467", - "0x200d", - "0x1f466", - "0x1f469", - "0x200d", - "0x1f469", - "0x200d", - "0x1f466", - "0x200d", - "0x1f466", - "0x1f469", - "0x200d", - "0x1f469", - "0x200d", - "0x1f467", - "0x200d", - "0x1f467", - "0x1f468", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f466", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f467", - "0x1f468", - "0x200d", - "0x1f467", - "0x200d", - "0x1f466", - "0x1f468", - "0x200d", - "0x1f467", - "0x200d", - "0x1f467", - "0x1f469", - "0x200d", - "0x1f466", - "0x1f469", - "0x200d", - "0x1f466", - "0x200d", - "0x1f466", - "0x1f469", - "0x200d", - "0x1f467", - "0x1f469", - "0x200d", - "0x1f467", - "0x200d", - "0x1f466", - "0x1f469", - "0x200d", - "0x1f467", - "0x200d", - "0x1f467", - "0x1f5e3", - "0x1f464", - "0x1f465", - "0x1fac2", - "0x1f46a", - "0x1f9d1", - "0x200d", - "0x1f9d1", - "0x200d", - "0x1f9d2", - "0x1f9d1", - "0x200d", - "0x1f9d1", - "0x200d", - "0x1f9d2", - "0x200d", - "0x1f9d2", - "0x1f9d1", - "0x200d", - "0x1f9d2", - "0x1f9d1", - "0x200d", - "0x1f9d2", - "0x200d", - "0x1f9d2", - "0x1f463" - ], - "animals_and_nature": [ - "0x1f435", - "0x1f412", - "0x1f98d", - "0x1f9a7", - "0x1f436", - "0x1f415", - "0x1f9ae", - "0x1f415", - "0x200d", - "0x1f9ba", - "0x1f429", - "0x1f43a", - "0x1f98a", - "0x1f99d", - "0x1f431", - "0x1f408", - "0x1f408", - "0x200d", - "0x2b1b", - "0x1f981", - "0x1f42f", - "0x1f405", - "0x1f406", - "0x1f434", - "0x1face", - "0x1facf", - "0x1f40e", - "0x1f984", - "0x1f993", - "0x1f98c", - "0x1f9ac", - "0x1f42e", - "0x1f402", - "0x1f403", - "0x1f404", - "0x1f437", - "0x1f416", - "0x1f417", - "0x1f43d", - "0x1f40f", - "0x1f411", - "0x1f410", - "0x1f42a", - "0x1f42b", - "0x1f999", - "0x1f992", - "0x1f418", - "0x1f9a3", - "0x1f98f", - "0x1f99b", - "0x1f42d", - "0x1f401", - "0x1f400", - "0x1f439", - "0x1f430", - "0x1f407", - "0x1f43f", - "0x1f9ab", - "0x1f994", - "0x1f987", - "0x1f43b", - "0x1f43b", - "0x200d", - "0x2744", - "0xfe0f", - "0x1f428", - "0x1f43c", - "0x1f9a5", - "0x1f9a6", - "0x1f9a8", - "0x1f998", - "0x1f9a1", - "0x1f43e", - "0x1f983", - "0x1f414", - "0x1f413", - "0x1f423", - "0x1f424", - "0x1f425", - "0x1f426", - "0x1f427", - "0x1f54a", - "0x1f985", - "0x1f986", - "0x1f9a2", - "0x1f989", - "0x1f9a4", - "0x1fab6", - "0x1f9a9", - "0x1f99a", - "0x1f99c", - "0x1fabd", - "0x1f426", - "0x200d", - "0x2b1b", - "0x1fabf", - "0x1f426", - "0x200d", - "0x1f525", - "0x1f438", - "0x1f40a", - "0x1f422", - "0x1f98e", - "0x1f40d", - "0x1f432", - "0x1f409", - "0x1f995", - "0x1f996", - "0x1f433", - "0x1f40b", - "0x1f42c", - "0x1f9ad", - "0x1f41f", - "0x1f420", - "0x1f421", - "0x1f988", - "0x1f419", - "0x1f41a", - "0x1fab8", - "0x1fabc", - "0x1f40c", - "0x1f98b", - "0x1f41b", - "0x1f41c", - "0x1f41d", - "0x1fab2", - "0x1f41e", - "0x1f997", - "0x1fab3", - "0x1f577", - "0x1f578", - "0x1f982", - "0x1f99f", - "0x1fab0", - "0x1fab1", - "0x1f9a0", - "0x1f490", - "0x1f338", - "0x1f4ae", - "0x1fab7", - "0x1f3f5", - "0x1f339", - "0x1f940", - "0x1f33a", - "0x1f33b", - "0x1f33c", - "0x1f337", - "0x1fabb", - "0x1f331", - "0x1fab4", - "0x1f332", - "0x1f333", - "0x1f334", - "0x1f335", - "0x1f33e", - "0x1f33f", - "0x2618", - "0x1f340", - "0x1f341", - "0x1f342", - "0x1f343", - "0x1fab9", - "0x1faba", - "0x1f344" - ], - "food_and_drink": [ - "0x1f347", - "0x1f348", - "0x1f349", - "0x1f34a", - "0x1f34b", - "0x1f34b", - "0x200d", - "0x1f7e9", - "0x1f34c", - "0x1f34d", - "0x1f96d", - "0x1f34e", - "0x1f34f", - "0x1f350", - "0x1f351", - "0x1f352", - "0x1f353", - "0x1fad0", - "0x1f95d", - "0x1f345", - "0x1fad2", - "0x1f965", - "0x1f951", - "0x1f346", - "0x1f954", - "0x1f955", - "0x1f33d", - "0x1f336", - "0x1fad1", - "0x1f952", - "0x1f96c", - "0x1f966", - "0x1f9c4", - "0x1f9c5", - "0x1f95c", - "0x1fad8", - "0x1f330", - "0x1fada", - "0x1fadb", - "0x1f344", - "0x200d", - "0x1f7eb", - "0x1f35e", - "0x1f950", - "0x1f956", - "0x1fad3", - "0x1f968", - "0x1f96f", - "0x1f95e", - "0x1f9c7", - "0x1f9c0", - "0x1f356", - "0x1f357", - "0x1f969", - "0x1f953", - "0x1f354", - "0x1f35f", - "0x1f355", - "0x1f32d", - "0x1f96a", - "0x1f32e", - "0x1f32f", - "0x1fad4", - "0x1f959", - "0x1f9c6", - "0x1f95a", - "0x1f373", - "0x1f958", - "0x1f372", - "0x1fad5", - "0x1f963", - "0x1f957", - "0x1f37f", - "0x1f9c8", - "0x1f9c2", - "0x1f96b", - "0x1f371", - "0x1f358", - "0x1f359", - "0x1f35a", - "0x1f35b", - "0x1f35c", - "0x1f35d", - "0x1f360", - "0x1f362", - "0x1f363", - "0x1f364", - "0x1f365", - "0x1f96e", - "0x1f361", - "0x1f95f", - "0x1f960", - "0x1f961", - "0x1f980", - "0x1f99e", - "0x1f990", - "0x1f991", - "0x1f9aa", - "0x1f366", - "0x1f367", - "0x1f368", - "0x1f369", - "0x1f36a", - "0x1f382", - "0x1f370", - "0x1f9c1", - "0x1f967", - "0x1f36b", - "0x1f36c", - "0x1f36d", - "0x1f36e", - "0x1f36f", - "0x1f37c", - "0x1f95b", - "0x2615", - "0x1fad6", - "0x1f375", - "0x1f376", - "0x1f37e", - "0x1f377", - "0x1f378", - "0x1f379", - "0x1f37a", - "0x1f37b", - "0x1f942", - "0x1f943", - "0x1fad7", - "0x1f964", - "0x1f9cb", - "0x1f9c3", - "0x1f9c9", - "0x1f9ca", - "0x1f962", - "0x1f37d", - "0x1f374", - "0x1f944", - "0x1f52a", - "0x1fad9", - "0x1f3fa" - ], - "travel_and_places": [ - "0x1f30d", - "0x1f30e", - "0x1f30f", - "0x1f310", - "0x1f5fa", - "0x1f5fe", - "0x1f9ed", - "0x1f3d4", - "0x26f0", - "0x1f30b", - "0x1f5fb", - "0x1f3d5", - "0x1f3d6", - "0x1f3dc", - "0x1f3dd", - "0x1f3de", - "0x1f3df", - "0x1f3db", - "0x1f3d7", - "0x1f9f1", - "0x1faa8", - "0x1fab5", - "0x1f6d6", - "0x1f3d8", - "0x1f3da", - "0x1f3e0", - "0x1f3e1", - "0x1f3e2", - "0x1f3e3", - "0x1f3e4", - "0x1f3e5", - "0x1f3e6", - "0x1f3e8", - "0x1f3e9", - "0x1f3ea", - "0x1f3eb", - "0x1f3ec", - "0x1f3ed", - "0x1f3ef", - "0x1f3f0", - "0x1f492", - "0x1f5fc", - "0x1f5fd", - "0x26ea", - "0x1f54c", - "0x1f6d5", - "0x1f54d", - "0x26e9", - "0x1f54b", - "0x26f2", - "0x26fa", - "0x1f301", - "0x1f303", - "0x1f3d9", - "0x1f304", - "0x1f305", - "0x1f306", - "0x1f307", - "0x1f309", - "0x2668", - "0x1f3a0", - "0x1f6dd", - "0x1f3a1", - "0x1f3a2", - "0x1f488", - "0x1f3aa", - "0x1f682", - "0x1f683", - "0x1f684", - "0x1f685", - "0x1f686", - "0x1f687", - "0x1f688", - "0x1f689", - "0x1f68a", - "0x1f69d", - "0x1f69e", - "0x1f68b", - "0x1f68c", - "0x1f68d", - "0x1f68e", - "0x1f690", - "0x1f691", - "0x1f692", - "0x1f693", - "0x1f694", - "0x1f695", - "0x1f696", - "0x1f697", - "0x1f698", - "0x1f699", - "0x1f6fb", - "0x1f69a", - "0x1f69b", - "0x1f69c", - "0x1f3ce", - "0x1f3cd", - "0x1f6f5", - "0x1f9bd", - "0x1f9bc", - "0x1f6fa", - "0x1f6b2", - "0x1f6f4", - "0x1f6f9", - "0x1f6fc", - "0x1f68f", - "0x1f6e3", - "0x1f6e4", - "0x1f6e2", - "0x26fd", - "0x1f6de", - "0x1f6a8", - "0x1f6a5", - "0x1f6a6", - "0x1f6d1", - "0x1f6a7", - "0x2693", - "0x1f6df", - "0x26f5", - "0x1f6f6", - "0x1f6a4", - "0x1f6f3", - "0x26f4", - "0x1f6e5", - "0x1f6a2", - "0x2708", - "0x1f6e9", - "0x1f6eb", - "0x1f6ec", - "0x1fa82", - "0x1f4ba", - "0x1f681", - "0x1f69f", - "0x1f6a0", - "0x1f6a1", - "0x1f6f0", - "0x1f680", - "0x1f6f8", - "0x1f6ce", - "0x1f9f3", - "0x231b", - "0x23f3", - "0x231a", - "0x23f0", - "0x23f1", - "0x23f2", - "0x1f570", - "0x1f55b", - "0x1f567", - "0x1f550", - "0x1f55c", - "0x1f551", - "0x1f55d", - "0x1f552", - "0x1f55e", - "0x1f553", - "0x1f55f", - "0x1f554", - "0x1f560", - "0x1f555", - "0x1f561", - "0x1f556", - "0x1f562", - "0x1f557", - "0x1f563", - "0x1f558", - "0x1f564", - "0x1f559", - "0x1f565", - "0x1f55a", - "0x1f566", - "0x1f311", - "0x1f312", - "0x1f313", - "0x1f314", - "0x1f315", - "0x1f316", - "0x1f317", - "0x1f318", - "0x1f319", - "0x1f31a", - "0x1f31b", - "0x1f31c", - "0x1f321", - "0x2600", - "0x1f31d", - "0x1f31e", - "0x1fa90", - "0x2b50", - "0x1f31f", - "0x1f320", - "0x1f30c", - "0x2601", - "0x26c5", - "0x26c8", - "0x1f324", - "0x1f325", - "0x1f326", - "0x1f327", - "0x1f328", - "0x1f329", - "0x1f32a", - "0x1f32b", - "0x1f32c", - "0x1f300", - "0x1f308", - "0x1f302", - "0x2602", - "0x2614", - "0x26f1", - "0x26a1", - "0x2744", - "0x2603", - "0x26c4", - "0x2604", - "0x1f525", - "0x1f4a7", - "0x1f30a" - ], - "activities": [ - "0x1f383", - "0x1f384", - "0x1f386", - "0x1f387", - "0x1f9e8", - "0x2728", - "0x1f388", - "0x1f389", - "0x1f38a", - "0x1f38b", - "0x1f38d", - "0x1f38e", - "0x1f38f", - "0x1f390", - "0x1f391", - "0x1f9e7", - "0x1f380", - "0x1f381", - "0x1f397", - "0x1f39f", - "0x1f3ab", - "0x1f396", - "0x1f3c6", - "0x1f3c5", - "0x1f947", - "0x1f948", - "0x1f949", - "0x26bd", - "0x26be", - "0x1f94e", - "0x1f3c0", - "0x1f3d0", - "0x1f3c8", - "0x1f3c9", - "0x1f3be", - "0x1f94f", - "0x1f3b3", - "0x1f3cf", - "0x1f3d1", - "0x1f3d2", - "0x1f94d", - "0x1f3d3", - "0x1f3f8", - "0x1f94a", - "0x1f94b", - "0x1f945", - "0x26f3", - "0x26f8", - "0x1f3a3", - "0x1f93f", - "0x1f3bd", - "0x1f3bf", - "0x1f6f7", - "0x1f94c", - "0x1f3af", - "0x1fa80", - "0x1fa81", - "0x1f52b", - "0x1f3b1", - "0x1f52e", - "0x1fa84", - "0x1f3ae", - "0x1f579", - "0x1f3b0", - "0x1f3b2", - "0x1f9e9", - "0x1f9f8", - "0x1fa85", - "0x1faa9", - "0x1fa86", - "0x2660", - "0x2665", - "0x2666", - "0x2663", - "0x265f", - "0x1f0cf", - "0x1f004", - "0x1f3b4", - "0x1f3ad", - "0x1f5bc", - "0x1f3a8", - "0x1f9f5", - "0x1faa1", - "0x1f9f6", - "0x1faa2" - ], - "objects": [ - "0x1f453", - "0x1f576", - "0x1f97d", - "0x1f97c", - "0x1f9ba", - "0x1f454", - "0x1f455", - "0x1f456", - "0x1f9e3", - "0x1f9e4", - "0x1f9e5", - "0x1f9e6", - "0x1f457", - "0x1f458", - "0x1f97b", - "0x1fa71", - "0x1fa72", - "0x1fa73", - "0x1f459", - "0x1f45a", - "0x1faad", - "0x1f45b", - "0x1f45c", - "0x1f45d", - "0x1f6cd", - "0x1f392", - "0x1fa74", - "0x1f45e", - "0x1f45f", - "0x1f97e", - "0x1f97f", - "0x1f460", - "0x1f461", - "0x1fa70", - "0x1f462", - "0x1faae", - "0x1f451", - "0x1f452", - "0x1f3a9", - "0x1f393", - "0x1f9e2", - "0x1fa96", - "0x26d1", - "0x1f4ff", - "0x1f484", - "0x1f48d", - "0x1f48e", - "0x1f507", - "0x1f508", - "0x1f509", - "0x1f50a", - "0x1f4e2", - "0x1f4e3", - "0x1f4ef", - "0x1f514", - "0x1f515", - "0x1f3bc", - "0x1f3b5", - "0x1f3b6", - "0x1f399", - "0x1f39a", - "0x1f39b", - "0x1f3a4", - "0x1f3a7", - "0x1f4fb", - "0x1f3b7", - "0x1fa97", - "0x1f3b8", - "0x1f3b9", - "0x1f3ba", - "0x1f3bb", - "0x1fa95", - "0x1f941", - "0x1fa98", - "0x1fa87", - "0x1fa88", - "0x1f4f1", - "0x1f4f2", - "0x260e", - "0x1f4de", - "0x1f4df", - "0x1f4e0", - "0x1f50b", - "0x1faab", - "0x1f50c", - "0x1f4bb", - "0x1f5a5", - "0x1f5a8", - "0x2328", - "0x1f5b1", - "0x1f5b2", - "0x1f4bd", - "0x1f4be", - "0x1f4bf", - "0x1f4c0", - "0x1f9ee", - "0x1f3a5", - "0x1f39e", - "0x1f4fd", - "0x1f3ac", - "0x1f4fa", - "0x1f4f7", - "0x1f4f8", - "0x1f4f9", - "0x1f4fc", - "0x1f50d", - "0x1f50e", - "0x1f56f", - "0x1f4a1", - "0x1f526", - "0x1f3ee", - "0x1fa94", - "0x1f4d4", - "0x1f4d5", - "0x1f4d6", - "0x1f4d7", - "0x1f4d8", - "0x1f4d9", - "0x1f4da", - "0x1f4d3", - "0x1f4d2", - "0x1f4c3", - "0x1f4dc", - "0x1f4c4", - "0x1f4f0", - "0x1f5de", - "0x1f4d1", - "0x1f516", - "0x1f3f7", - "0x1f4b0", - "0x1fa99", - "0x1f4b4", - "0x1f4b5", - "0x1f4b6", - "0x1f4b7", - "0x1f4b8", - "0x1f4b3", - "0x1f9fe", - "0x1f4b9", - "0x2709", - "0x1f4e7", - "0x1f4e8", - "0x1f4e9", - "0x1f4e4", - "0x1f4e5", - "0x1f4e6", - "0x1f4eb", - "0x1f4ea", - "0x1f4ec", - "0x1f4ed", - "0x1f4ee", - "0x1f5f3", - "0x270f", - "0x2712", - "0x1f58b", - "0x1f58a", - "0x1f58c", - "0x1f58d", - "0x1f4dd", - "0x1f4bc", - "0x1f4c1", - "0x1f4c2", - "0x1f5c2", - "0x1f4c5", - "0x1f4c6", - "0x1f5d2", - "0x1f5d3", - "0x1f4c7", - "0x1f4c8", - "0x1f4c9", - "0x1f4ca", - "0x1f4cb", - "0x1f4cc", - "0x1f4cd", - "0x1f4ce", - "0x1f587", - "0x1f4cf", - "0x1f4d0", - "0x2702", - "0x1f5c3", - "0x1f5c4", - "0x1f5d1", - "0x1f512", - "0x1f513", - "0x1f50f", - "0x1f510", - "0x1f511", - "0x1f5dd", - "0x1f528", - "0x1fa93", - "0x26cf", - "0x2692", - "0x1f6e0", - "0x1f5e1", - "0x2694", - "0x1f4a3", - "0x1fa83", - "0x1f3f9", - "0x1f6e1", - "0x1fa9a", - "0x1f527", - "0x1fa9b", - "0x1f529", - "0x2699", - "0x1f5dc", - "0x2696", - "0x1f9af", - "0x1f517", - "0x26d3", - "0xfe0f", - "0x200d", - "0x1f4a5", - "0x26d3", - "0x1fa9d", - "0x1f9f0", - "0x1f9f2", - "0x1fa9c", - "0x2697", - "0x1f9ea", - "0x1f9eb", - "0x1f9ec", - "0x1f52c", - "0x1f52d", - "0x1f4e1", - "0x1f489", - "0x1fa78", - "0x1f48a", - "0x1fa79", - "0x1fa7c", - "0x1fa7a", - "0x1fa7b", - "0x1f6aa", - "0x1f6d7", - "0x1fa9e", - "0x1fa9f", - "0x1f6cf", - "0x1f6cb", - "0x1fa91", - "0x1f6bd", - "0x1faa0", - "0x1f6bf", - "0x1f6c1", - "0x1faa4", - "0x1fa92", - "0x1f9f4", - "0x1f9f7", - "0x1f9f9", - "0x1f9fa", - "0x1f9fb", - "0x1faa3", - "0x1f9fc", - "0x1fae7", - "0x1faa5", - "0x1f9fd", - "0x1f9ef", - "0x1f6d2", - "0x1f6ac", - "0x26b0", - "0x1faa6", - "0x26b1", - "0x1f9ff", - "0x1faac", - "0x1f5ff", - "0x1faa7", - "0x1faaa" - ], - "symbols": [ - "0x1f3e7", - "0x1f6ae", - "0x1f6b0", - "0x267f", - "0x1f6b9", - "0x1f6ba", - "0x1f6bb", - "0x1f6bc", - "0x1f6be", - "0x1f6c2", - "0x1f6c3", - "0x1f6c4", - "0x1f6c5", - "0x26a0", - "0x1f6b8", - "0x26d4", - "0x1f6ab", - "0x1f6b3", - "0x1f6ad", - "0x1f6af", - "0x1f6b1", - "0x1f6b7", - "0x1f4f5", - "0x1f51e", - "0x2622", - "0x2623", - "0x2b06", - "0x2197", - "0x27a1", - "0x2198", - "0x2b07", - "0x2199", - "0x2b05", - "0x2196", - "0x2195", - "0x2194", - "0x21a9", - "0x21aa", - "0x2934", - "0x2935", - "0x1f503", - "0x1f504", - "0x1f519", - "0x1f51a", - "0x1f51b", - "0x1f51c", - "0x1f51d", - "0x1f6d0", - "0x269b", - "0x1f549", - "0x2721", - "0x2638", - "0x262f", - "0x271d", - "0x2626", - "0x262a", - "0x262e", - "0x1f54e", - "0x1f52f", - "0x1faaf", - "0x2648", - "0x2649", - "0x264a", - "0x264b", - "0x264c", - "0x264d", - "0x264e", - "0x264f", - "0x2650", - "0x2651", - "0x2652", - "0x2653", - "0x26ce", - "0x1f500", - "0x1f501", - "0x1f502", - "0x25b6", - "0x23e9", - "0x23ed", - "0x23ef", - "0x25c0", - "0x23ea", - "0x23ee", - "0x1f53c", - "0x23eb", - "0x1f53d", - "0x23ec", - "0x23f8", - "0x23f9", - "0x23fa", - "0x23cf", - "0x1f3a6", - "0x1f505", - "0x1f506", - "0x1f4f6", - "0x1f6dc", - "0x1f4f3", - "0x1f4f4", - "0x2640", - "0x2642", - "0x26a7", - "0x2716", - "0x2795", - "0x2796", - "0x2797", - "0x1f7f0", - "0x267e", - "0x203c", - "0x2049", - "0x2753", - "0x2754", - "0x2755", - "0x2757", - "0x3030", - "0x1f4b1", - "0x1f4b2", - "0x2695", - "0x267b", - "0x269c", - "0x1f531", - "0x1f4db", - "0x1f530", - "0x2b55", - "0x2705", - "0x2611", - "0x2714", - "0x274c", - "0x274e", - "0x27b0", - "0x27bf", - "0x303d", - "0x2733", - "0x2734", - "0x2747", - "0x00a9", - "0x00ae", - "0x2122", - "0x0023", - "0xfe0f", - "0x20e3", - "0x002a", - "0xfe0f", - "0x20e3", - "0x0030", - "0xfe0f", - "0x20e3", - "0x0031", - "0xfe0f", - "0x20e3", - "0x0032", - "0xfe0f", - "0x20e3", - "0x0033", - "0xfe0f", - "0x20e3", - "0x0034", - "0xfe0f", - "0x20e3", - "0x0035", - "0xfe0f", - "0x20e3", - "0x0036", - "0xfe0f", - "0x20e3", - "0x0037", - "0xfe0f", - "0x20e3", - "0x0038", - "0xfe0f", - "0x20e3", - "0x0039", - "0xfe0f", - "0x20e3", - "0x1f51f", - "0x1f520", - "0x1f521", - "0x1f522", - "0x1f523", - "0x1f524", - "0x1f170", - "0x1f18e", - "0x1f171", - "0x1f191", - "0x1f192", - "0x1f193", - "0x2139", - "0x1f194", - "0x24c2", - "0x1f195", - "0x1f196", - "0x1f17e", - "0x1f197", - "0x1f17f", - "0x1f198", - "0x1f199", - "0x1f19a", - "0x1f201", - "0x1f202", - "0x1f237", - "0x1f236", - "0x1f22f", - "0x1f250", - "0x1f239", - "0x1f21a", - "0x1f232", - "0x1f251", - "0x1f238", - "0x1f234", - "0x1f233", - "0x3297", - "0x3299", - "0x1f23a", - "0x1f235", - "0x1f534", - "0x1f7e0", - "0x1f7e1", - "0x1f7e2", - "0x1f535", - "0x1f7e3", - "0x1f7e4", - "0x26ab", - "0x26aa", - "0x1f7e5", - "0x1f7e7", - "0x1f7e8", - "0x1f7e9", - "0x1f7e6", - "0x1f7ea", - "0x1f7eb", - "0x2b1b", - "0x2b1c", - "0x25fc", - "0x25fb", - "0x25fe", - "0x25fd", - "0x25aa", - "0x25ab", - "0x1f536", - "0x1f537", - "0x1f538", - "0x1f539", - "0x1f53a", - "0x1f53b", - "0x1f4a0", - "0x1f518", - "0x1f533", - "0x1f532" - ], - "flags": [ - "0x1f3c1", - "0x1f6a9", - "0x1f38c", - "0x1f3f4", - "0x1f3f3", - "0x1f3f3", - "0xfe0f", - "0x200d", - "0x1f308", - "0x1f3f3", - "0xfe0f", - "0x200d", - "0x26a7", - "0xfe0f", - "0x1f3f4", - "0x200d", - "0x2620", - "0xfe0f", - "0x1f1e6", - "0x1f1e8", - "0x1f1e6", - "0x1f1e9", - "0x1f1e6", - "0x1f1ea", - "0x1f1e6", - "0x1f1eb", - "0x1f1e6", - "0x1f1ec", - "0x1f1e6", - "0x1f1ee", - "0x1f1e6", - "0x1f1f1", - "0x1f1e6", - "0x1f1f2", - "0x1f1e6", - "0x1f1f4", - "0x1f1e6", - "0x1f1f6", - "0x1f1e6", - "0x1f1f7", - "0x1f1e6", - "0x1f1f8", - "0x1f1e6", - "0x1f1f9", - "0x1f1e6", - "0x1f1fa", - "0x1f1e6", - "0x1f1fc", - "0x1f1e6", - "0x1f1fd", - "0x1f1e6", - "0x1f1ff", - "0x1f1e7", - "0x1f1e6", - "0x1f1e7", - "0x1f1e7", - "0x1f1e7", - "0x1f1e9", - "0x1f1e7", - "0x1f1ea", - "0x1f1e7", - "0x1f1eb", - "0x1f1e7", - "0x1f1ec", - "0x1f1e7", - "0x1f1ed", - "0x1f1e7", - "0x1f1ee", - "0x1f1e7", - "0x1f1ef", - "0x1f1e7", - "0x1f1f1", - "0x1f1e7", - "0x1f1f2", - "0x1f1e7", - "0x1f1f3", - "0x1f1e7", - "0x1f1f4", - "0x1f1e7", - "0x1f1f6", - "0x1f1e7", - "0x1f1f7", - "0x1f1e7", - "0x1f1f8", - "0x1f1e7", - "0x1f1f9", - "0x1f1e7", - "0x1f1fb", - "0x1f1e7", - "0x1f1fc", - "0x1f1e7", - "0x1f1fe", - "0x1f1e7", - "0x1f1ff", - "0x1f1e8", - "0x1f1e6", - "0x1f1e8", - "0x1f1e8", - "0x1f1e8", - "0x1f1e9", - "0x1f1e8", - "0x1f1eb", - "0x1f1e8", - "0x1f1ec", - "0x1f1e8", - "0x1f1ed", - "0x1f1e8", - "0x1f1ee", - "0x1f1e8", - "0x1f1f0", - "0x1f1e8", - "0x1f1f1", - "0x1f1e8", - "0x1f1f2", - "0x1f1e8", - "0x1f1f3", - "0x1f1e8", - "0x1f1f4", - "0x1f1e8", - "0x1f1f5", - "0x1f1e8", - "0x1f1f7", - "0x1f1e8", - "0x1f1fa", - "0x1f1e8", - "0x1f1fb", - "0x1f1e8", - "0x1f1fc", - "0x1f1e8", - "0x1f1fd", - "0x1f1e8", - "0x1f1fe", - "0x1f1e8", - "0x1f1ff", - "0x1f1e9", - "0x1f1ea", - "0x1f1e9", - "0x1f1ec", - "0x1f1e9", - "0x1f1ef", - "0x1f1e9", - "0x1f1f0", - "0x1f1e9", - "0x1f1f2", - "0x1f1e9", - "0x1f1f4", - "0x1f1e9", - "0x1f1ff", - "0x1f1ea", - "0x1f1e6", - "0x1f1ea", - "0x1f1e8", - "0x1f1ea", - "0x1f1ea", - "0x1f1ea", - "0x1f1ec", - "0x1f1ea", - "0x1f1ed", - "0x1f1ea", - "0x1f1f7", - "0x1f1ea", - "0x1f1f8", - "0x1f1ea", - "0x1f1f9", - "0x1f1ea", - "0x1f1fa", - "0x1f1eb", - "0x1f1ee", - "0x1f1eb", - "0x1f1ef", - "0x1f1eb", - "0x1f1f0", - "0x1f1eb", - "0x1f1f2", - "0x1f1eb", - "0x1f1f4", - "0x1f1eb", - "0x1f1f7", - "0x1f1ec", - "0x1f1e6", - "0x1f1ec", - "0x1f1e7", - "0x1f1ec", - "0x1f1e9", - "0x1f1ec", - "0x1f1ea", - "0x1f1ec", - "0x1f1eb", - "0x1f1ec", - "0x1f1ec", - "0x1f1ec", - "0x1f1ed", - "0x1f1ec", - "0x1f1ee", - "0x1f1ec", - "0x1f1f1", - "0x1f1ec", - "0x1f1f2", - "0x1f1ec", - "0x1f1f3", - "0x1f1ec", - "0x1f1f5", - "0x1f1ec", - "0x1f1f6", - "0x1f1ec", - "0x1f1f7", - "0x1f1ec", - "0x1f1f8", - "0x1f1ec", - "0x1f1f9", - "0x1f1ec", - "0x1f1fa", - "0x1f1ec", - "0x1f1fc", - "0x1f1ec", - "0x1f1fe", - "0x1f1ed", - "0x1f1f0", - "0x1f1ed", - "0x1f1f2", - "0x1f1ed", - "0x1f1f3", - "0x1f1ed", - "0x1f1f7", - "0x1f1ed", - "0x1f1f9", - "0x1f1ed", - "0x1f1fa", - "0x1f1ee", - "0x1f1e8", - "0x1f1ee", - "0x1f1e9", - "0x1f1ee", - "0x1f1ea", - "0x1f1ee", - "0x1f1f1", - "0x1f1ee", - "0x1f1f2", - "0x1f1ee", - "0x1f1f3", - "0x1f1ee", - "0x1f1f4", - "0x1f1ee", - "0x1f1f6", - "0x1f1ee", - "0x1f1f7", - "0x1f1ee", - "0x1f1f8", - "0x1f1ee", - "0x1f1f9", - "0x1f1ef", - "0x1f1ea", - "0x1f1ef", - "0x1f1f2", - "0x1f1ef", - "0x1f1f4", - "0x1f1ef", - "0x1f1f5", - "0x1f1f0", - "0x1f1ea", - "0x1f1f0", - "0x1f1ec", - "0x1f1f0", - "0x1f1ed", - "0x1f1f0", - "0x1f1ee", - "0x1f1f0", - "0x1f1f2", - "0x1f1f0", - "0x1f1f3", - "0x1f1f0", - "0x1f1f5", - "0x1f1f0", - "0x1f1f7", - "0x1f1f0", - "0x1f1fc", - "0x1f1f0", - "0x1f1fe", - "0x1f1f0", - "0x1f1ff", - "0x1f1f1", - "0x1f1e6", - "0x1f1f1", - "0x1f1e7", - "0x1f1f1", - "0x1f1e8", - "0x1f1f1", - "0x1f1ee", - "0x1f1f1", - "0x1f1f0", - "0x1f1f1", - "0x1f1f7", - "0x1f1f1", - "0x1f1f8", - "0x1f1f1", - "0x1f1f9", - "0x1f1f1", - "0x1f1fa", - "0x1f1f1", - "0x1f1fb", - "0x1f1f1", - "0x1f1fe", - "0x1f1f2", - "0x1f1e6", - "0x1f1f2", - "0x1f1e8", - "0x1f1f2", - "0x1f1e9", - "0x1f1f2", - "0x1f1ea", - "0x1f1f2", - "0x1f1eb", - "0x1f1f2", - "0x1f1ec", - "0x1f1f2", - "0x1f1ed", - "0x1f1f2", - "0x1f1f0", - "0x1f1f2", - "0x1f1f1", - "0x1f1f2", - "0x1f1f2", - "0x1f1f2", - "0x1f1f3", - "0x1f1f2", - "0x1f1f4", - "0x1f1f2", - "0x1f1f5", - "0x1f1f2", - "0x1f1f6", - "0x1f1f2", - "0x1f1f7", - "0x1f1f2", - "0x1f1f8", - "0x1f1f2", - "0x1f1f9", - "0x1f1f2", - "0x1f1fa", - "0x1f1f2", - "0x1f1fb", - "0x1f1f2", - "0x1f1fc", - "0x1f1f2", - "0x1f1fd", - "0x1f1f2", - "0x1f1fe", - "0x1f1f2", - "0x1f1ff", - "0x1f1f3", - "0x1f1e6", - "0x1f1f3", - "0x1f1e8", - "0x1f1f3", - "0x1f1ea", - "0x1f1f3", - "0x1f1eb", - "0x1f1f3", - "0x1f1ec", - "0x1f1f3", - "0x1f1ee", - "0x1f1f3", - "0x1f1f1", - "0x1f1f3", - "0x1f1f4", - "0x1f1f3", - "0x1f1f5", - "0x1f1f3", - "0x1f1f7", - "0x1f1f3", - "0x1f1fa", - "0x1f1f3", - "0x1f1ff", - "0x1f1f4", - "0x1f1f2", - "0x1f1f5", - "0x1f1e6", - "0x1f1f5", - "0x1f1ea", - "0x1f1f5", - "0x1f1eb", - "0x1f1f5", - "0x1f1ec", - "0x1f1f5", - "0x1f1ed", - "0x1f1f5", - "0x1f1f0", - "0x1f1f5", - "0x1f1f1", - "0x1f1f5", - "0x1f1f2", - "0x1f1f5", - "0x1f1f3", - "0x1f1f5", - "0x1f1f7", - "0x1f1f5", - "0x1f1f8", - "0x1f1f5", - "0x1f1f9", - "0x1f1f5", - "0x1f1fc", - "0x1f1f5", - "0x1f1fe", - "0x1f1f6", - "0x1f1e6", - "0x1f1f7", - "0x1f1ea", - "0x1f1f7", - "0x1f1f4", - "0x1f1f7", - "0x1f1f8", - "0x1f1f7", - "0x1f1fa", - "0x1f1f7", - "0x1f1fc", - "0x1f1f8", - "0x1f1e6", - "0x1f1f8", - "0x1f1e7", - "0x1f1f8", - "0x1f1e8", - "0x1f1f8", - "0x1f1e9", - "0x1f1f8", - "0x1f1ea", - "0x1f1f8", - "0x1f1ec", - "0x1f1f8", - "0x1f1ed", - "0x1f1f8", - "0x1f1ee", - "0x1f1f8", - "0x1f1ef", - "0x1f1f8", - "0x1f1f0", - "0x1f1f8", - "0x1f1f1", - "0x1f1f8", - "0x1f1f2", - "0x1f1f8", - "0x1f1f3", - "0x1f1f8", - "0x1f1f4", - "0x1f1f8", - "0x1f1f7", - "0x1f1f8", - "0x1f1f8", - "0x1f1f8", - "0x1f1f9", - "0x1f1f8", - "0x1f1fb", - "0x1f1f8", - "0x1f1fd", - "0x1f1f8", - "0x1f1fe", - "0x1f1f8", - "0x1f1ff", - "0x1f1f9", - "0x1f1e6", - "0x1f1f9", - "0x1f1e8", - "0x1f1f9", - "0x1f1e9", - "0x1f1f9", - "0x1f1eb", - "0x1f1f9", - "0x1f1ec", - "0x1f1f9", - "0x1f1ed", - "0x1f1f9", - "0x1f1ef", - "0x1f1f9", - "0x1f1f0", - "0x1f1f9", - "0x1f1f1", - "0x1f1f9", - "0x1f1f2", - "0x1f1f9", - "0x1f1f3", - "0x1f1f9", - "0x1f1f4", - "0x1f1f9", - "0x1f1f7", - "0x1f1f9", - "0x1f1f9", - "0x1f1f9", - "0x1f1fb", - "0x1f1f9", - "0x1f1fc", - "0x1f1f9", - "0x1f1ff", - "0x1f1fa", - "0x1f1e6", - "0x1f1fa", - "0x1f1ec", - "0x1f1fa", - "0x1f1f2", - "0x1f1fa", - "0x1f1f3", - "0x1f1fa", - "0x1f1f8", - "0x1f1fa", - "0x1f1fe", - "0x1f1fa", - "0x1f1ff", - "0x1f1fb", - "0x1f1e6", - "0x1f1fb", - "0x1f1e8", - "0x1f1fb", - "0x1f1ea", - "0x1f1fb", - "0x1f1ec", - "0x1f1fb", - "0x1f1ee", - "0x1f1fb", - "0x1f1f3", - "0x1f1fb", - "0x1f1fa", - "0x1f1fc", - "0x1f1eb", - "0x1f1fc", - "0x1f1f8", - "0x1f1fd", - "0x1f1f0", - "0x1f1fe", - "0x1f1ea", - "0x1f1fe", - "0x1f1f9", - "0x1f1ff", - "0x1f1e6", - "0x1f1ff", - "0x1f1f2", - "0x1f1ff", - "0x1f1fc", - "0x1f3f4", - "0xe0067", - "0xe0062", - "0xe0065", - "0xe006e", - "0xe0067", - "0xe007f", - "0x1f3f4", - "0xe0067", - "0xe0062", - "0xe0073", - "0xe0063", - "0xe0074", - "0xe007f", - "0x1f3f4", - "0xe0067", - "0xe0062", - "0xe0077", - "0xe006c", - "0xe0073", - "0xe007f" - ] - } - }; - - var o_hasOwnProperty = Object.prototype.hasOwnProperty; - var o_keys = (Object.keys || function(obj) { - var result = []; - for (var key in obj) { - if (o_hasOwnProperty.call(obj, key)) { - result.push(key); - } - } - - return result; - }); - - - function _copyObject(source, target) { - var keys = o_keys(source); - var key; - - for (var i = 0, l = keys.length; i < l; i++) { - key = keys[i]; - target[key] = source[key] || target[key]; - } - } - - function _copyArray(source, target) { - for (var i = 0, l = source.length; i < l; i++) { - target[i] = source[i]; - } - } - - function copyObject(source, _target) { - var isArray = Array.isArray(source); - var target = _target || (isArray ? new Array(source.length) : {}); - - if (isArray) { - _copyArray(source, target); - } else { - _copyObject(source, target); - } - - return target; - } - - /** Get the data based on key**/ - Chance.prototype.get = function (name) { - return copyObject(data[name]); - }; - - // Mac Address - Chance.prototype.mac_address = function(options){ - // typically mac addresses are separated by ":" - // however they can also be separated by "-" - // the network variant uses a dot every fourth byte - - options = initOptions(options); - if(!options.separator) { - options.separator = options.networkVersion ? "." : ":"; - } - - var mac_pool="ABCDEF1234567890", - mac = ""; - if(!options.networkVersion) { - mac = this.n(this.string, 6, { pool: mac_pool, length:2 }).join(options.separator); - } else { - mac = this.n(this.string, 3, { pool: mac_pool, length:4 }).join(options.separator); - } - - return mac; - }; - - Chance.prototype.normal = function (options) { - options = initOptions(options, {mean : 0, dev : 1, pool : []}); - - testRange( - options.pool.constructor !== Array, - "Chance: The pool option must be a valid array." - ); - testRange( - typeof options.mean !== 'number', - "Chance: Mean (mean) must be a number" - ); - testRange( - typeof options.dev !== 'number', - "Chance: Standard deviation (dev) must be a number" - ); - - // If a pool has been passed, then we are returning an item from that pool, - // using the normal distribution settings that were passed in - if (options.pool.length > 0) { - return this.normal_pool(options); - } - - // The Marsaglia Polar method - var s, u, v, norm, - mean = options.mean, - dev = options.dev; - - do { - // U and V are from the uniform distribution on (-1, 1) - u = this.random() * 2 - 1; - v = this.random() * 2 - 1; - - s = u * u + v * v; - } while (s >= 1); - - // Compute the standard normal variate - norm = u * Math.sqrt(-2 * Math.log(s) / s); - - // Shape and scale - return dev * norm + mean; - }; - - Chance.prototype.normal_pool = function(options) { - var performanceCounter = 0; - do { - var idx = Math.round(this.normal({ mean: options.mean, dev: options.dev })); - if (idx < options.pool.length && idx >= 0) { - return options.pool[idx]; - } else { - performanceCounter++; - } - } while(performanceCounter < 100); - - throw new RangeError("Chance: Your pool is too small for the given mean and standard deviation. Please adjust."); - }; - - Chance.prototype.radio = function (options) { - // Initial Letter (Typically Designated by Side of Mississippi River) - options = initOptions(options, {side : "?"}); - var fl = ""; - switch (options.side.toLowerCase()) { - case "east": - case "e": - fl = "W"; - break; - case "west": - case "w": - fl = "K"; - break; - default: - fl = this.character({pool: "KW"}); - break; - } - - return fl + this.character({alpha: true, casing: "upper"}) + - this.character({alpha: true, casing: "upper"}) + - this.character({alpha: true, casing: "upper"}); - }; - - // Set the data as key and data or the data map - Chance.prototype.set = function (name, values) { - if (typeof name === "string") { - data[name] = values; - } else { - data = copyObject(name, data); - } - }; - - Chance.prototype.tv = function (options) { - return this.radio(options); - }; - - // ID number for Brazil companies - Chance.prototype.cnpj = function () { - var n = this.n(this.natural, 8, { max: 9 }); - var d1 = 2+n[7]*6+n[6]*7+n[5]*8+n[4]*9+n[3]*2+n[2]*3+n[1]*4+n[0]*5; - d1 = 11 - (d1 % 11); - if (d1>=10){ - d1 = 0; - } - var d2 = d1*2+3+n[7]*7+n[6]*8+n[5]*9+n[4]*2+n[3]*3+n[2]*4+n[1]*5+n[0]*6; - d2 = 11 - (d2 % 11); - if (d2>=10){ - d2 = 0; - } - return ''+n[0]+n[1]+'.'+n[2]+n[3]+n[4]+'.'+n[5]+n[6]+n[7]+'/0001-'+d1+d2; - }; - - Chance.prototype.emotion = function () { - return this.pick(this.get("emotions")); - }; - - // -- End Miscellaneous -- - - Chance.prototype.mersenne_twister = function (seed) { - return new MersenneTwister(seed); - }; - - Chance.prototype.blueimp_md5 = function () { - return new BlueImpMD5(); - }; - - // Mersenne Twister from https://gist.github.com/banksean/300494 - /* - A C-program for MT19937, with initialization improved 2002/1/26. - Coded by Takuji Nishimura and Makoto Matsumoto. - - Before using, initialize the state by using init_genrand(seed) - or init_by_array(init_key, key_length). - - Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. The names of its contributors may not be used to endorse or promote - products derived from this software without specific prior written - permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - Any feedback is very welcome. - http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html - email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) - */ - var MersenneTwister = function (seed) { - if (seed === undefined) { - // kept random number same size as time used previously to ensure no unexpected results downstream - seed = Math.floor(Math.random()*Math.pow(10,13)); - } - /* Period parameters */ - this.N = 624; - this.M = 397; - this.MATRIX_A = 0x9908b0df; /* constant vector a */ - this.UPPER_MASK = 0x80000000; /* most significant w-r bits */ - this.LOWER_MASK = 0x7fffffff; /* least significant r bits */ - - this.mt = new Array(this.N); /* the array for the state vector */ - this.mti = this.N + 1; /* mti==N + 1 means mt[N] is not initialized */ - - this.init_genrand(seed); - }; - - /* initializes mt[N] with a seed */ - MersenneTwister.prototype.init_genrand = function (s) { - this.mt[0] = s >>> 0; - for (this.mti = 1; this.mti < this.N; this.mti++) { - s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30); - this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253) + this.mti; - /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ - /* In the previous versions, MSBs of the seed affect */ - /* only MSBs of the array mt[]. */ - /* 2002/01/09 modified by Makoto Matsumoto */ - this.mt[this.mti] >>>= 0; - /* for >32 bit machines */ - } - }; - - /* initialize by an array with array-length */ - /* init_key is the array for initializing keys */ - /* key_length is its length */ - /* slight change for C++, 2004/2/26 */ - MersenneTwister.prototype.init_by_array = function (init_key, key_length) { - var i = 1, j = 0, k, s; - this.init_genrand(19650218); - k = (this.N > key_length ? this.N : key_length); - for (; k; k--) { - s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30); - this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + ((s & 0x0000ffff) * 1664525))) + init_key[j] + j; /* non linear */ - this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */ - i++; - j++; - if (i >= this.N) { this.mt[0] = this.mt[this.N - 1]; i = 1; } - if (j >= key_length) { j = 0; } - } - for (k = this.N - 1; k; k--) { - s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30); - this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941)) - i; /* non linear */ - this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */ - i++; - if (i >= this.N) { this.mt[0] = this.mt[this.N - 1]; i = 1; } - } - - this.mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */ - }; - - /* generates a random number on [0,0xffffffff]-interval */ - MersenneTwister.prototype.genrand_int32 = function () { - var y; - var mag01 = new Array(0x0, this.MATRIX_A); - /* mag01[x] = x * MATRIX_A for x=0,1 */ - - if (this.mti >= this.N) { /* generate N words at one time */ - var kk; - - if (this.mti === this.N + 1) { /* if init_genrand() has not been called, */ - this.init_genrand(5489); /* a default initial seed is used */ - } - for (kk = 0; kk < this.N - this.M; kk++) { - y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk + 1]&this.LOWER_MASK); - this.mt[kk] = this.mt[kk + this.M] ^ (y >>> 1) ^ mag01[y & 0x1]; - } - for (;kk < this.N - 1; kk++) { - y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk + 1]&this.LOWER_MASK); - this.mt[kk] = this.mt[kk + (this.M - this.N)] ^ (y >>> 1) ^ mag01[y & 0x1]; - } - y = (this.mt[this.N - 1]&this.UPPER_MASK)|(this.mt[0]&this.LOWER_MASK); - this.mt[this.N - 1] = this.mt[this.M - 1] ^ (y >>> 1) ^ mag01[y & 0x1]; - - this.mti = 0; - } - - y = this.mt[this.mti++]; - - /* Tempering */ - y ^= (y >>> 11); - y ^= (y << 7) & 0x9d2c5680; - y ^= (y << 15) & 0xefc60000; - y ^= (y >>> 18); - - return y >>> 0; - }; - - /* generates a random number on [0,0x7fffffff]-interval */ - MersenneTwister.prototype.genrand_int31 = function () { - return (this.genrand_int32() >>> 1); - }; - - /* generates a random number on [0,1]-real-interval */ - MersenneTwister.prototype.genrand_real1 = function () { - return this.genrand_int32() * (1.0 / 4294967295.0); - /* divided by 2^32-1 */ - }; - - /* generates a random number on [0,1)-real-interval */ - MersenneTwister.prototype.random = function () { - return this.genrand_int32() * (1.0 / 4294967296.0); - /* divided by 2^32 */ - }; - - /* generates a random number on (0,1)-real-interval */ - MersenneTwister.prototype.genrand_real3 = function () { - return (this.genrand_int32() + 0.5) * (1.0 / 4294967296.0); - /* divided by 2^32 */ - }; - - /* generates a random number on [0,1) with 53-bit resolution*/ - MersenneTwister.prototype.genrand_res53 = function () { - var a = this.genrand_int32()>>>5, b = this.genrand_int32()>>>6; - return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0); - }; - - // BlueImp MD5 hashing algorithm from https://github.com/blueimp/JavaScript-MD5 - var BlueImpMD5 = function () {}; - - BlueImpMD5.prototype.VERSION = '1.0.1'; - - /* - * Add integers, wrapping at 2^32. This uses 16-bit operations internally - * to work around bugs in some JS interpreters. - */ - BlueImpMD5.prototype.safe_add = function safe_add(x, y) { - var lsw = (x & 0xFFFF) + (y & 0xFFFF), - msw = (x >> 16) + (y >> 16) + (lsw >> 16); - return (msw << 16) | (lsw & 0xFFFF); - }; - - /* - * Bitwise rotate a 32-bit number to the left. - */ - BlueImpMD5.prototype.bit_roll = function (num, cnt) { - return (num << cnt) | (num >>> (32 - cnt)); - }; - - /* - * These functions implement the five basic operations the algorithm uses. - */ - BlueImpMD5.prototype.md5_cmn = function (q, a, b, x, s, t) { - return this.safe_add(this.bit_roll(this.safe_add(this.safe_add(a, q), this.safe_add(x, t)), s), b); - }; - BlueImpMD5.prototype.md5_ff = function (a, b, c, d, x, s, t) { - return this.md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); - }; - BlueImpMD5.prototype.md5_gg = function (a, b, c, d, x, s, t) { - return this.md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); - }; - BlueImpMD5.prototype.md5_hh = function (a, b, c, d, x, s, t) { - return this.md5_cmn(b ^ c ^ d, a, b, x, s, t); - }; - BlueImpMD5.prototype.md5_ii = function (a, b, c, d, x, s, t) { - return this.md5_cmn(c ^ (b | (~d)), a, b, x, s, t); - }; - - /* - * Calculate the MD5 of an array of little-endian words, and a bit length. - */ - BlueImpMD5.prototype.binl_md5 = function (x, len) { - /* append padding */ - x[len >> 5] |= 0x80 << (len % 32); - x[(((len + 64) >>> 9) << 4) + 14] = len; - - var i, olda, oldb, oldc, oldd, - a = 1732584193, - b = -271733879, - c = -1732584194, - d = 271733878; - - for (i = 0; i < x.length; i += 16) { - olda = a; - oldb = b; - oldc = c; - oldd = d; - - a = this.md5_ff(a, b, c, d, x[i], 7, -680876936); - d = this.md5_ff(d, a, b, c, x[i + 1], 12, -389564586); - c = this.md5_ff(c, d, a, b, x[i + 2], 17, 606105819); - b = this.md5_ff(b, c, d, a, x[i + 3], 22, -1044525330); - a = this.md5_ff(a, b, c, d, x[i + 4], 7, -176418897); - d = this.md5_ff(d, a, b, c, x[i + 5], 12, 1200080426); - c = this.md5_ff(c, d, a, b, x[i + 6], 17, -1473231341); - b = this.md5_ff(b, c, d, a, x[i + 7], 22, -45705983); - a = this.md5_ff(a, b, c, d, x[i + 8], 7, 1770035416); - d = this.md5_ff(d, a, b, c, x[i + 9], 12, -1958414417); - c = this.md5_ff(c, d, a, b, x[i + 10], 17, -42063); - b = this.md5_ff(b, c, d, a, x[i + 11], 22, -1990404162); - a = this.md5_ff(a, b, c, d, x[i + 12], 7, 1804603682); - d = this.md5_ff(d, a, b, c, x[i + 13], 12, -40341101); - c = this.md5_ff(c, d, a, b, x[i + 14], 17, -1502002290); - b = this.md5_ff(b, c, d, a, x[i + 15], 22, 1236535329); - - a = this.md5_gg(a, b, c, d, x[i + 1], 5, -165796510); - d = this.md5_gg(d, a, b, c, x[i + 6], 9, -1069501632); - c = this.md5_gg(c, d, a, b, x[i + 11], 14, 643717713); - b = this.md5_gg(b, c, d, a, x[i], 20, -373897302); - a = this.md5_gg(a, b, c, d, x[i + 5], 5, -701558691); - d = this.md5_gg(d, a, b, c, x[i + 10], 9, 38016083); - c = this.md5_gg(c, d, a, b, x[i + 15], 14, -660478335); - b = this.md5_gg(b, c, d, a, x[i + 4], 20, -405537848); - a = this.md5_gg(a, b, c, d, x[i + 9], 5, 568446438); - d = this.md5_gg(d, a, b, c, x[i + 14], 9, -1019803690); - c = this.md5_gg(c, d, a, b, x[i + 3], 14, -187363961); - b = this.md5_gg(b, c, d, a, x[i + 8], 20, 1163531501); - a = this.md5_gg(a, b, c, d, x[i + 13], 5, -1444681467); - d = this.md5_gg(d, a, b, c, x[i + 2], 9, -51403784); - c = this.md5_gg(c, d, a, b, x[i + 7], 14, 1735328473); - b = this.md5_gg(b, c, d, a, x[i + 12], 20, -1926607734); - - a = this.md5_hh(a, b, c, d, x[i + 5], 4, -378558); - d = this.md5_hh(d, a, b, c, x[i + 8], 11, -2022574463); - c = this.md5_hh(c, d, a, b, x[i + 11], 16, 1839030562); - b = this.md5_hh(b, c, d, a, x[i + 14], 23, -35309556); - a = this.md5_hh(a, b, c, d, x[i + 1], 4, -1530992060); - d = this.md5_hh(d, a, b, c, x[i + 4], 11, 1272893353); - c = this.md5_hh(c, d, a, b, x[i + 7], 16, -155497632); - b = this.md5_hh(b, c, d, a, x[i + 10], 23, -1094730640); - a = this.md5_hh(a, b, c, d, x[i + 13], 4, 681279174); - d = this.md5_hh(d, a, b, c, x[i], 11, -358537222); - c = this.md5_hh(c, d, a, b, x[i + 3], 16, -722521979); - b = this.md5_hh(b, c, d, a, x[i + 6], 23, 76029189); - a = this.md5_hh(a, b, c, d, x[i + 9], 4, -640364487); - d = this.md5_hh(d, a, b, c, x[i + 12], 11, -421815835); - c = this.md5_hh(c, d, a, b, x[i + 15], 16, 530742520); - b = this.md5_hh(b, c, d, a, x[i + 2], 23, -995338651); - - a = this.md5_ii(a, b, c, d, x[i], 6, -198630844); - d = this.md5_ii(d, a, b, c, x[i + 7], 10, 1126891415); - c = this.md5_ii(c, d, a, b, x[i + 14], 15, -1416354905); - b = this.md5_ii(b, c, d, a, x[i + 5], 21, -57434055); - a = this.md5_ii(a, b, c, d, x[i + 12], 6, 1700485571); - d = this.md5_ii(d, a, b, c, x[i + 3], 10, -1894986606); - c = this.md5_ii(c, d, a, b, x[i + 10], 15, -1051523); - b = this.md5_ii(b, c, d, a, x[i + 1], 21, -2054922799); - a = this.md5_ii(a, b, c, d, x[i + 8], 6, 1873313359); - d = this.md5_ii(d, a, b, c, x[i + 15], 10, -30611744); - c = this.md5_ii(c, d, a, b, x[i + 6], 15, -1560198380); - b = this.md5_ii(b, c, d, a, x[i + 13], 21, 1309151649); - a = this.md5_ii(a, b, c, d, x[i + 4], 6, -145523070); - d = this.md5_ii(d, a, b, c, x[i + 11], 10, -1120210379); - c = this.md5_ii(c, d, a, b, x[i + 2], 15, 718787259); - b = this.md5_ii(b, c, d, a, x[i + 9], 21, -343485551); - - a = this.safe_add(a, olda); - b = this.safe_add(b, oldb); - c = this.safe_add(c, oldc); - d = this.safe_add(d, oldd); - } - return [a, b, c, d]; - }; - - /* - * Convert an array of little-endian words to a string - */ - BlueImpMD5.prototype.binl2rstr = function (input) { - var i, - output = ''; - for (i = 0; i < input.length * 32; i += 8) { - output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF); - } - return output; - }; - - /* - * Convert a raw string to an array of little-endian words - * Characters >255 have their high-byte silently ignored. - */ - BlueImpMD5.prototype.rstr2binl = function (input) { - var i, - output = []; - output[(input.length >> 2) - 1] = undefined; - for (i = 0; i < output.length; i += 1) { - output[i] = 0; - } - for (i = 0; i < input.length * 8; i += 8) { - output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32); - } - return output; - }; - - /* - * Calculate the MD5 of a raw string - */ - BlueImpMD5.prototype.rstr_md5 = function (s) { - return this.binl2rstr(this.binl_md5(this.rstr2binl(s), s.length * 8)); - }; - - /* - * Calculate the HMAC-MD5, of a key and some data (raw strings) - */ - BlueImpMD5.prototype.rstr_hmac_md5 = function (key, data) { - var i, - bkey = this.rstr2binl(key), - ipad = [], - opad = [], - hash; - ipad[15] = opad[15] = undefined; - if (bkey.length > 16) { - bkey = this.binl_md5(bkey, key.length * 8); - } - for (i = 0; i < 16; i += 1) { - ipad[i] = bkey[i] ^ 0x36363636; - opad[i] = bkey[i] ^ 0x5C5C5C5C; - } - hash = this.binl_md5(ipad.concat(this.rstr2binl(data)), 512 + data.length * 8); - return this.binl2rstr(this.binl_md5(opad.concat(hash), 512 + 128)); - }; - - /* - * Convert a raw string to a hex string - */ - BlueImpMD5.prototype.rstr2hex = function (input) { - var hex_tab = '0123456789abcdef', - output = '', - x, - i; - for (i = 0; i < input.length; i += 1) { - x = input.charCodeAt(i); - output += hex_tab.charAt((x >>> 4) & 0x0F) + - hex_tab.charAt(x & 0x0F); - } - return output; - }; - - /* - * Encode a string as utf-8 - */ - BlueImpMD5.prototype.str2rstr_utf8 = function (input) { - return unescape(encodeURIComponent(input)); - }; - - /* - * Take string arguments and return either raw or hex encoded strings - */ - BlueImpMD5.prototype.raw_md5 = function (s) { - return this.rstr_md5(this.str2rstr_utf8(s)); - }; - BlueImpMD5.prototype.hex_md5 = function (s) { - return this.rstr2hex(this.raw_md5(s)); - }; - BlueImpMD5.prototype.raw_hmac_md5 = function (k, d) { - return this.rstr_hmac_md5(this.str2rstr_utf8(k), this.str2rstr_utf8(d)); - }; - BlueImpMD5.prototype.hex_hmac_md5 = function (k, d) { - return this.rstr2hex(this.raw_hmac_md5(k, d)); - }; - - BlueImpMD5.prototype.md5 = function (string, key, raw) { - if (!key) { - if (!raw) { - return this.hex_md5(string); - } - - return this.raw_md5(string); - } - - if (!raw) { - return this.hex_hmac_md5(key, string); - } - - return this.raw_hmac_md5(key, string); - }; - - // CommonJS module - if (true) { - if ( true && module.exports) { - exports = module.exports = Chance; - } - exports.Chance = Chance; - } - - // Register as an anonymous AMD module - if (typeof define === 'function' && define.amd) { - define([], function () { - return Chance; - }); - } - - // if there is a importsScrips object define chance for worker - // allows worker to use full Chance functionality with seed - if (typeof importScripts !== 'undefined') { - chance = new Chance(); - self.Chance = Chance; - } - - // If there is a window object, that at least has a document property, - // instantiate and define chance on the window - if (typeof window === "object" && typeof window.document === "object") { - window.Chance = Chance; - window.chance = new Chance(); - } -})(); - - /***/ }), /***/ 77755: @@ -51057,7 +39352,6 @@ const github = __importStar(__nccwpck_require__(78227)); const configuration_handler_1 = __nccwpck_require__(40188); const git_cli_repository_1 = __nccwpck_require__(26331); const issue_content_composition_root_1 = __nccwpck_require__(62255); -const issue_interaction_composition_root_1 = __nccwpck_require__(92503); const project_board_composition_root_1 = __nccwpck_require__(37194); const github_action_completion_1 = __nccwpck_require__(54140); const github_action_input_1 = __nccwpck_require__(8824); @@ -51078,7 +39372,7 @@ const actor_authorization_composition_root_1 = __nccwpck_require__(233); const application_error_context_1 = __nccwpck_require__(4034); const application_error_1 = __nccwpck_require__(75999); const application_error_presentation_policy_1 = __nccwpck_require__(95067); -const shared_capability_port_binding_1 = __nccwpck_require__(47399); +const push_single_action_capability_port_binding_1 = __nccwpck_require__(49417); async function runGitHubAction() { const eventInputs = (0, github_event_inputs_1.buildGithubActionEventInputs)({ payload: github.context.payload, @@ -51138,7 +39432,7 @@ async function runGitHubAction() { const results = await (0, common_action_1.mainRun)(execution, projectBoard.command, new git_cli_repository_1.GitCliRepository(token), 'github-workflow', (0, lifecycle_state_composition_root_1.createSynchronizeLifecycleStateUseCase)(repositoryBinding), (0, agent_activity_composition_root_1.createSynchronizeAgentActivityUseCase)(repositoryBinding)); const issueContentPort = (0, issue_content_composition_root_1.createIssueContentCompositionRoot)(); const configurationHandler = new configuration_handler_1.ConfigurationHandler(issueContentPort); - await (0, github_action_completion_1.finishGithubAction)(execution, results, (0, shared_capability_port_binding_1.bindIssueNotification)((0, issue_interaction_composition_root_1.createIssueNotificationRepository)(), repositoryBinding), { + await (0, github_action_completion_1.finishGithubAction)(execution, results, (0, push_single_action_capability_port_binding_1.bindIssueCommentPublication)(issueContentPort, repositoryBinding), { update: (context) => configurationHandler.update({ ...repositoryBinding, issueNumber: context.issueNumber, @@ -51298,7 +39592,6 @@ const store_configuration_use_case_1 = __nccwpck_require__(84375); const publish_resume_workflow_1 = __nccwpck_require__(55340); const store_configuration_use_case_2 = __nccwpck_require__(84375); const logger_1 = __nccwpck_require__(91151); -const logger_adapter_1 = __nccwpck_require__(72762); const action_summary_policy_1 = __nccwpck_require__(72995); const copilot_lifecycle_1 = __nccwpck_require__(72418); const copilot_evidence_policy_1 = __nccwpck_require__(47632); @@ -51316,7 +39609,7 @@ async function finishGithubAction(execution, results, issueNotificationPort, con const dryRun = results.some((result) => (0, result_1.getResultPayload)(result.payload)?.dryRun === true); const ownsDeploymentPresentation = execution.singleAction.isDeploymentOrchestrationAction; if (!dryRun && !execution.singleAction.isPublishIssueCommentAction && !ownsDeploymentPresentation) { - const publicationFailure = await new publish_resume_use_case_1.PublishResultUseCase(issueNotificationPort, (0, logger_adapter_1.createLogReportAdapter)()).invoke((0, publish_resume_workflow_1.projectPublishResultContext)(execution)); + const publicationFailure = await new publish_resume_use_case_1.PublishResultUseCase(issueNotificationPort).invoke((0, publish_resume_workflow_1.projectPublishResultContext)(execution)); if (publicationFailure) results.push(publicationFailure); } @@ -53872,12 +42165,14 @@ exports.isStaleBranchSyncComment = isStaleBranchSyncComment; exports.buildStaleBranchSyncComment = buildStaleBranchSyncComment; exports.buildAlignedBranchSyncComment = buildAlignedBranchSyncComment; const github_user_policy_1 = __nccwpck_require__(84403); -exports.BRANCH_SYNC_STALE_MARKER = ""; -exports.BRANCH_SYNC_ALIGNED_MARKER = ""; -const BRANCH_SYNC_KEY_MARKER = "'; +exports.BRANCH_SYNC_ALIGNED_MARKER = ''; +const BRANCH_SYNC_KEY_MARKER = ''; const SAFE_GITHUB_USERNAME = /^[A-Za-z0-9-]+$/u; @@ -55181,8 +43476,39 @@ function normalizeCopilotBotUsername(username) { : exports.DEFAULT_COPILOT_BOT_USERNAME; } /** Renders the stable command reference used by /copilot help. */ -function buildCopilotHelpMessage(username) { +function buildCopilotHelpMessage(username, locale = 'en-US') { const bot = normalizeCopilotBotUsername(username); + if ((0, locale_1.baseLanguage)(locale) === 'es') + return `## Comandos de Copilot + +Soy **@${bot}**, el asistente del repositorio. Usa estos comandos en una issue o pull request: + +### Solo lectura + +- \`/copilot help\` — muestra esta referencia. +- \`/copilot plan\` — propone un plan de implementación. +- \`/copilot clarify\` — identifica información pendiente y supuestos. +- \`/copilot estimate\` — estima el alcance y la complejidad. +- \`/copilot test-plan\` — propone una estrategia de pruebas. +- \`/copilot explain \` — explica código o comportamiento. +- \`/copilot diagnose\` — investiga un problema y sus posibles causas. +- \`/copilot analyze\` — analiza la issue, rama o pull request actual. +- \`/copilot review [effort=smart|low|default|high] [dry-run=true] [trace-rules=true] [suggested-changes=false]\` — ejecuta Bugbot con ajustes opcionales. +- \`/copilot findings\` — muestra los hallazgos potenciales. +- \`/copilot recheck\` — repite la revisión y reconcilia los hallazgos. +- \`/copilot description\` — actualiza la descripción del pull request. +- \`/copilot status\` — muestra el estado actual de la automatización. + +### Cambios + +- \`/copilot fix \` — corrige un hallazgo. +- \`/copilot fix all\` — corrige todos los hallazgos sin resolver. +- \`/copilot dismiss \` — descarta un hallazgo. +- \`/copilot remember \` — añade una regla de revisión autorizada y versionada. +- \`/copilot implement \` — aplica un cambio solicitado explícitamente. +- \`/copilot sync-branch [--dry-run] [--no-agent] [--from ]\` — integra la rama padre de forma segura. + +También puedes mencionar a **@${bot}** y escribir una pregunta. Los comandos que modifican archivos están limitados a mantenedores autorizados, ejecutan las comprobaciones configuradas e informan de los cambios resultantes.`; return `## Copilot commands I’m **@${bot}**, the repository assistant. Use these commands on an issue or pull request: @@ -55215,8 +43541,16 @@ I’m **@${bot}**, the repository assistant. Use these commands on an issue or p You can also ask a question in natural language by mentioning **@${bot}**. For example: “@${bot} update the issue's branch”. File-changing commands are restricted to authorized maintainers, run the configured checks, and report the resulting changes.`; } /** Renders the one-time onboarding comment for a newly created issue. */ -function buildCopilotWelcomeMessage(username) { +function buildCopilotWelcomeMessage(username, locale = 'en-US') { const bot = normalizeCopilotBotUsername(username); + if ((0, locale_1.baseLanguage)(locale) === 'es') + return `${exports.COPILOT_WELCOME_MARKER} + +Hola, soy **@${bot}**, el asistente de Copilot de este repositorio. + +Puedo responder preguntas, explicar el código, proponer planes de implementación y pruebas, revisar issues y pull requests y ayudar a los mantenedores autorizados a aplicar cambios. + +Usa \`/copilot help\` para ver los comandos disponibles o menciona a **@${bot}** con tu pregunta.`; return `${exports.COPILOT_WELCOME_MARKER} Hi! I’m **@${bot}**, the Copilot assistant for this repository. @@ -55233,6 +43567,7 @@ function buildCopilotWelcomeResult(username) { executed: true, stepFormat: 'markdown', steps: [buildCopilotWelcomeMessage(username)], + payload: Object.freeze({ publication: Object.freeze({ kind: 'welcome', botLogin: normalizeCopilotBotUsername(username) }) }), }); } @@ -55572,6 +43907,7 @@ exports.renderReconciliationPullRequest = renderReconciliationPullRequest; exports.renderDeploymentJobSummary = renderDeploymentJobSummary; exports.normalizeLocale = normalizeLocale; const managed_pull_request_1 = __nccwpck_require__(95914); +const publication_identity_policy_1 = __nccwpck_require__(45403); const EN = { release: "Release", hotfix: "Hotfix", currentStatus: "Current status", noAction: "No action is required while GitHub owns the pending transition.", actionRequired: "Action required", @@ -55639,6 +43975,15 @@ function renderDeploymentDashboard(operation, context) { const title = operation.kind === "release" ? messages.release : messages.hotfix; const action = deploymentAction(operation, messages); const lines = [ + (0, publication_identity_policy_1.buildPublicationMarker)({ + identity: { + topic: 'release', + target: { kind: 'issue', number: context.issue }, + key: `operation:${(0, publication_identity_policy_1.createSemanticDigest)(operation.operationId)}`, + }, + sourceVersion: `revision:${operation.revision}`, + digest: (0, publication_identity_policy_1.createSemanticDigest)({ locale: context.issueLocale, operation }), + }), deploymentDashboardMarker(operation.operationId, context.issue), "", `# ${operation.phase === "blocked" ? "❌" : operation.phase === "completed" ? "✅" : "🚀"} ${title} ${inline(operation.version)}`, "", `> **${messages.currentStatus}: ${messages.phase[operation.phase]}.**`, @@ -56282,6 +44627,155 @@ function progressPercentToColor(percent) { } +/***/ }), + +/***/ 45403: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.PUBLICATION_REPLY_MARKER_PREFIX = exports.PUBLICATION_DUPLICATE_MARKER_PREFIX = exports.PUBLICATION_MARKER_PREFIX = exports.PUBLICATION_SCHEMA = void 0; +exports.createSemanticDigest = createSemanticDigest; +exports.buildPublicationMarker = buildPublicationMarker; +exports.parsePublicationMarker = parsePublicationMarker; +exports.buildPublicationReplyMarker = buildPublicationReplyMarker; +exports.parsePublicationReplyMarker = parsePublicationReplyMarker; +exports.buildDuplicateMarker = buildDuplicateMarker; +const node_crypto_1 = __nccwpck_require__(6005); +const github_publication_1 = __nccwpck_require__(35793); +exports.PUBLICATION_SCHEMA = '1'; +exports.PUBLICATION_MARKER_PREFIX = 'copilot:publication'; +exports.PUBLICATION_DUPLICATE_MARKER_PREFIX = 'copilot:publication-duplicate'; +exports.PUBLICATION_REPLY_MARKER_PREFIX = 'copilot:reply'; +const SAFE_VALUE = /^[A-Za-z0-9._:-]{1,128}$/u; +const DIGEST = /^[a-f0-9]{8,64}$/u; +function createSemanticDigest(value) { + return (0, node_crypto_1.createHash)('sha256').update(stableSerialize(value), 'utf8').digest('hex').slice(0, 16); +} +function buildPublicationMarker(marker) { + const target = (0, github_publication_1.publicationTargetToken)(marker.identity.target); + for (const value of [marker.identity.topic, target, marker.identity.key, marker.sourceVersion]) { + if (!SAFE_VALUE.test(value)) + throw new Error('Publication marker contains an unsafe identity value.'); + } + if (!DIGEST.test(marker.digest)) + throw new Error('Publication marker contains an invalid digest.'); + return ``; +} +function parsePublicationMarker(body) { + if (typeof body !== 'string') + return undefined; + const match = body.match(//u); + if (!match) + return undefined; + const topic = match[1]; + if (!github_publication_1.PUBLICATION_TOPICS.includes(topic)) + return undefined; + const number = Number(match[3]); + if (!Number.isSafeInteger(number) || number < 1) + return undefined; + return Object.freeze({ + identity: Object.freeze({ + topic, + target: Object.freeze({ kind: match[2] === 'pr' ? 'pull-request' : 'issue', number }), + key: match[4], + }), + sourceVersion: match[5], + digest: match[6], + }); +} +function buildPublicationReplyMarker(marker) { + for (const value of [marker.target, marker.correlationId, marker.messageKey]) { + if (!SAFE_VALUE.test(value)) + throw new Error('Publication reply marker contains an unsafe identity value.'); + } + if (!DIGEST.test(marker.digest)) + throw new Error('Publication reply marker contains an invalid digest.'); + return ``; +} +function parsePublicationReplyMarker(body) { + if (typeof body !== 'string') + return undefined; + const match = body.match(//u); + if (!match) + return undefined; + return Object.freeze({ target: match[1], correlationId: match[2], messageKey: match[3], digest: match[4] }); +} +function buildDuplicateMarker(canonicalCommentId) { + if (!Number.isSafeInteger(canonicalCommentId) || canonicalCommentId < 1) { + throw new Error('Canonical comment id must be a positive integer.'); + } + return ``; +} +function stableSerialize(value) { + if (Array.isArray(value)) + return `[${value.map(stableSerialize).join(',')}]`; + if (value && typeof value === 'object') { + const record = value; + return `{${Object.keys(record).sort().map(key => `${JSON.stringify(key)}:${stableSerialize(record[key])}`).join(',')}}`; + } + return JSON.stringify(value) ?? 'null'; +} + + +/***/ }), + +/***/ 34223: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.SPANISH_PUBLICATION_CATALOG = exports.ENGLISH_PUBLICATION_CATALOG = void 0; +exports.resolveStaticPublicationCatalog = resolveStaticPublicationCatalog; +const locale_1 = __nccwpck_require__(15386); +const ENGLISH = Object.freeze({ + locale: 'en-US', + implementationPlan: 'Implementation plan', + planReady: 'Ready to start. No action is required from maintainers before implementation.', + planAcceptance: 'Acceptance', + commandsHint: 'Need something else? Mention the bot with a question or use `/copilot help`.', + progress: 'Progress', + progressState: Object.freeze({ 'not-started': 'not started', 'in-progress': 'in progress', complete: 'complete' }), + currentStatus: 'Current status', + next: 'Next', + noActionRequired: 'No action required.', + supersededStatus: 'This status was superseded by the canonical card.', + viewCurrentStatus: 'View current status', + duplicateReply: 'This duplicate response was suppressed.', + viewOriginalResponse: 'View the original response', +}); +const SPANISH = Object.freeze({ + locale: 'es-ES', + implementationPlan: 'Plan de implementación', + planReady: 'Listo para comenzar. No se requiere ninguna acción de mantenimiento antes de la implementación.', + planAcceptance: 'Aceptación', + commandsHint: '¿Necesitas algo más? Menciona al bot con una pregunta o usa `/copilot help`.', + progress: 'Progreso', + progressState: Object.freeze({ 'not-started': 'sin iniciar', 'in-progress': 'en curso', complete: 'completado' }), + currentStatus: 'Estado actual', + next: 'Siguiente paso', + noActionRequired: 'No se requiere ninguna acción.', + supersededStatus: 'Este estado fue sustituido por la tarjeta canónica.', + viewCurrentStatus: 'Ver estado actual', + duplicateReply: 'Esta respuesta duplicada se ha omitido.', + viewOriginalResponse: 'Ver la respuesta original', +}); +function resolveStaticPublicationCatalog(locale) { + const requestedLocale = (0, locale_1.canonicalizeLocaleTag)(locale || locale_1.DEFAULT_REPOSITORY_LOCALE); + if ((0, locale_1.baseLanguage)(requestedLocale) === 'es') { + return Object.freeze({ requestedLocale, catalog: SPANISH, fallback: false }); + } + if ((0, locale_1.baseLanguage)(requestedLocale) === 'en') { + return Object.freeze({ requestedLocale, catalog: ENGLISH, fallback: false }); + } + return Object.freeze({ requestedLocale, catalog: ENGLISH, fallback: true }); +} +exports.ENGLISH_PUBLICATION_CATALOG = ENGLISH; +exports.SPANISH_PUBLICATION_CATALOG = SPANISH; + + /***/ }), /***/ 39410: @@ -56340,180 +44834,6 @@ function createSha256(value) { } -/***/ }), - -/***/ 71512: -/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.resolveResultPublicationIssueNumber = resolveResultPublicationIssueNumber; -exports.resolveResultPublicationPresentation = resolveResultPublicationPresentation; -exports.renderResultSections = renderResultSections; -exports.buildDebugLogSection = buildDebugLogSection; -exports.hasPublishableContent = hasPublishableContent; -const github_comment_publication_policy_1 = __nccwpck_require__(72712); -const result_publication_presentation_policy_1 = __nccwpck_require__(98227); -const result_publication_sections_policy_1 = __nccwpck_require__(20954); -const MAX_DEBUG_LOG_LENGTH = 12000; -/** Resolves the GitHub discussion that receives a result comment. */ -function resolveResultPublicationIssueNumber(input) { - if (input.isSingleAction) - return input.singleActionIssue > 0 ? input.singleActionIssue : undefined; - if (input.isIssue) - return input.issueNumber; - if (input.isPullRequest) - return input.pullRequestNumber; - if (input.isPush && input.pushIssueNumber > 0) - return input.pushIssueNumber; - return undefined; -} -function resolveResultPublicationPresentation(context, selectImage) { - return (0, result_publication_presentation_policy_1.selectResultPublicationPresentation)(context, selectImage); -} -function renderResultSections(results) { - return (0, result_publication_sections_policy_1.renderResultSections)(results); -} -function buildDebugLogSection(debug, logsText) { - if (!debug || logsText.length === 0) - return ''; - const safeLogs = (0, github_comment_publication_policy_1.sanitizeAgentMarkdown)(logsText, MAX_DEBUG_LOG_LENGTH).replace(/```/g, ''); - if (!safeLogs.trim()) - return ''; - return ` - -
-Debug log - -\`\`\` -${safeLogs} -\`\`\` -
-`; -} -function hasPublishableContent(sections, debugLogSection) { - // Debug output belongs in the job summary/logs. Publishing it as the only - // result creates a comment with just the presentation GIF and metadata, - // which can retrigger `issue_comment` workflows indefinitely. - // Reminders are supplementary guidance and must accompany primary result - // content; they are not a result by themselves. - void debugLogSection; - return sections.content.trim().length > 0 - || sections.errors.trim().length > 0; -} - - -/***/ }), - -/***/ 98227: -/***/ ((__unused_webpack_module, exports) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.selectResultPublicationPresentation = selectResultPublicationPresentation; -function selectResultPublicationPresentation(context, selectImage) { - if (context.isIssue) - return selectIssuePresentation(context, selectImage); - if (context.isPullRequest) - return selectPullRequestPresentation(context, selectImage); - return { title: '🪄 Automatic Actions' }; -} -function selectIssuePresentation(context, selectImage) { - if (context.issueNotBranched) - return presentation('🪄 Automatic Actions', context.images.issueAutomaticActions, selectImage); - if (context.releaseActive) - return presentation('🚀 Release Actions', context.images.issueReleaseGifs, selectImage); - if (context.hotfixActive) - return presentation('🔥🐛 Hotfix Actions', context.images.issueHotfixGifs, selectImage); - if (context.isBugfix) - return presentation('🐛 Bugfix Actions', context.images.issueBugfixGifs, selectImage); - if (context.isFeature) - return presentation('✨ Feature Actions', context.images.issueFeatureGifs, selectImage); - if (context.isDocs) - return presentation('📝 Documentation Actions', context.images.issueDocsGifs, selectImage); - if (context.isChore) - return presentation('🔧 Chore Actions', context.images.issueChoreGifs, selectImage); - return { title: '🪄 Automatic Actions' }; -} -function selectPullRequestPresentation(context, selectImage) { - if (context.releaseActive) - return presentation('🚀 Release Actions', context.images.pullRequestReleaseGifs, selectImage); - if (context.hotfixActive) - return presentation('🔥🐛 Hotfix Actions', context.images.pullRequestHotfixGifs, selectImage); - if (context.isBugfix) - return presentation('🐛 Bugfix Actions', context.images.pullRequestBugfixGifs, selectImage); - if (context.isFeature) - return presentation('✨ Feature Actions', context.images.pullRequestFeatureGifs, selectImage); - if (context.isDocs) - return presentation('📝 Documentation Actions', context.images.pullRequestDocsGifs, selectImage); - if (context.isChore) - return presentation('🔧 Chore Actions', context.images.pullRequestChoreGifs, selectImage); - return presentation('🪄 Automatic Actions', context.images.pullRequestAutomaticActions, selectImage); -} -function presentation(title, images, selectImage) { - return { title, image: selectImage(images) }; -} - - -/***/ }), - -/***/ 20954: -/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.renderResultSections = renderResultSections; -const comment_content_policy_1 = __nccwpck_require__(77454); -const github_comment_publication_policy_1 = __nccwpck_require__(72712); -function renderResultSections(results) { - const renderedSteps = []; - const reminders = []; - const errors = []; - let stepIndex = 0; - for (const result of results) { - stepIndex = appendSteps(renderedSteps, result, stepIndex); - reminders.push(...result.reminders - .map(reminder => (0, github_comment_publication_policy_1.sanitizeAgentMarkdown)(reminder)) - .filter(comment_content_policy_1.hasVisibleCommentContent)); - errors.push(...result.errors - .map(renderPublishedApplicationError) - .filter(comment_content_policy_1.hasVisibleCommentContent)); - } - return { - content: renderedSteps.length > 0 ? `${renderedSteps.join('\n\n')}\n` : '', - footer: reminders.length > 0 ? `\n## Reminder\n\n${reminders.map((reminder, index) => `${index + 1}. ${reminder}`).join('\n')}\n` : '', - errors: errors.length > 0 - ? `\n## Errors Found\n\n${errors.map((error, index) => `${index + 1}. ${error}\n`).join('\n')}\nCheck your project configuration, if everything is okay consider [opening an issue](https://github.com/vypdev/copilot/issues/new/choose).\n` - : '', - }; -} -function renderPublishedApplicationError(view) { - return [ - `**Impact:** ${(0, github_comment_publication_policy_1.sanitizePublishedError)(view.impact)}`, - ` **Cause (\`${view.code}\`):** ${(0, github_comment_publication_policy_1.sanitizePublishedError)(view.cause)}`, - ` **Action:** ${(0, github_comment_publication_policy_1.sanitizePublishedError)(view.action)}`, - ` **Retained state:** ${(0, github_comment_publication_policy_1.sanitizePublishedError)(view.retainedState)}`, - ` **Reference:** \`${view.reference}\``, - ].join('\n'); -} -function appendSteps(renderedSteps, result, stepIndex) { - for (const step of result.steps) { - if (!step.trim()) - continue; - const safeStep = (0, github_comment_publication_policy_1.sanitizeAgentMarkdown)(step); - if (!safeStep.trim()) - continue; - renderedSteps.push(result.stepFormat === 'markdown' ? safeStep : `${stepIndex + 1}. ${safeStep}`); - if (result.stepFormat !== 'markdown') - stepIndex += 1; - } - return stepIndex; -} - - /***/ }), /***/ 88350: @@ -56561,6 +44881,232 @@ function calculateReviewersStillNeeded(desiredCount, currentCount, confirmedCoun } +/***/ }), + +/***/ 81985: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.selectSemanticStatusIntents = selectSemanticStatusIntents; +exports.selectSemanticReplyIntents = selectSemanticReplyIntents; +exports.renderSemanticReply = renderSemanticReply; +exports.renderSemanticStatus = renderSemanticStatus; +const github_publication_1 = __nccwpck_require__(35793); +const result_1 = __nccwpck_require__(73817); +const github_comment_publication_policy_1 = __nccwpck_require__(72712); +const publication_identity_policy_1 = __nccwpck_require__(45403); +const publication_message_catalog_1 = __nccwpck_require__(34223); +const copilot_interaction_policy_1 = __nccwpck_require__(90108); +const status_command_policy_1 = __nccwpck_require__(3449); +function selectSemanticStatusIntents(context) { + return Object.freeze(context.results.flatMap(result => { + if (!result.executed || !result.success) + return []; + const payload = (0, result_1.getResultPayload)(result.payload); + if (!payload) + return []; + const plan = planIntent(result.id, payload, context.locale); + if (plan) + return [plan]; + const progress = progressIntent(result.id, payload, context.locale); + return progress ? [progress] : []; + })); +} +function selectSemanticReplyIntents(context) { + if (!context.target || !positiveInteger(context.target.number) || !context.correlationId?.trim()) + return []; + const correlationId = safeMarkerToken(context.correlationId); + return Object.freeze(context.results.flatMap(result => { + if (!result.executed || !result.success) + return []; + const payload = (0, result_1.getResultPayload)(result.payload); + if (!payload) + return []; + const publication = (0, result_1.getResultPayload)(payload.publication); + const kind = publication?.kind; + if (kind === 'help' || kind === 'welcome') { + const botLogin = typeof publication?.botLogin === 'string' && publication.botLogin.trim() + ? publication.botLogin.trim() + : context.botLogin?.trim() ?? ''; + const projection = Object.freeze({ kind, botLogin }); + return [replyIntent(context, correlationId, `copilot-${kind}`, projection)]; + } + if (kind === 'access-policy') { + return [replyIntent(context, correlationId, 'access-policy', Object.freeze({ kind }))]; + } + const snapshot = (0, result_1.getResultPayload)(payload.status); + if (result.id.endsWith('.Status') && isStatusSnapshot(snapshot)) { + const projection = Object.freeze({ + kind: 'status-command', + snapshot: copyStatusSnapshot(snapshot), + }); + return [replyIntent(context, correlationId, 'copilot-status', projection)]; + } + return []; + })); +} +function renderSemanticReply(intent) { + const marker = (0, publication_identity_policy_1.buildPublicationReplyMarker)({ + target: (0, github_publication_1.publicationTargetToken)(intent.target), + correlationId: intent.correlationId, + messageKey: intent.messageKey, + digest: intent.digest, + }); + const body = intent.projection.kind === 'help' + ? (0, copilot_interaction_policy_1.buildCopilotHelpMessage)(intent.projection.botLogin, intent.locale) + : intent.projection.kind === 'welcome' + ? (0, copilot_interaction_policy_1.buildCopilotWelcomeMessage)(intent.projection.botLogin, intent.locale) + : intent.projection.kind === 'access-policy' + ? renderAccessPolicyReply(intent.locale) + : (0, status_command_policy_1.formatCopilotStatus)(intent.projection.snapshot, intent.locale); + return `${marker}\n\n${body}`; +} +function renderAccessPolicyReply(locale) { + if ((0, publication_message_catalog_1.resolveStaticPublicationCatalog)(locale).catalog.locale === 'es-ES') { + return [ + '## Issue cerrada: se requiere acceso de colaborador', + '', + 'Este repositorio solo permite el procesamiento automatizado de issues creadas por colaboradores autorizados.', + '', + 'Si crees que se trata de un error, contacta con un mantenedor o consulta la política de contribución del repositorio.', + ].join('\n'); + } + return [ + '## Issue closed: contributor access required', + '', + 'This repository accepts automated issue processing only from eligible contributors.', + '', + 'If you believe this is incorrect, contact a maintainer or follow the repository contribution policy.', + ].join('\n'); +} +function renderSemanticStatus(intent) { + const messages = (0, publication_message_catalog_1.resolveStaticPublicationCatalog)(intent.locale).catalog; + const marker = (0, publication_identity_policy_1.buildPublicationMarker)(intent); + if (intent.projection.kind === 'plan') { + const plan = (0, github_comment_publication_policy_1.sanitizeAgentMarkdown)(intent.projection.recommendation, 8000).trim(); + return [ + marker, + '', + `## ${messages.implementationPlan}`, + '', + `> **${messages.currentStatus}:** ${messages.planReady}`, + '', + plan, + '', + `**${messages.planAcceptance}:** ${messages.noActionRequired}`, + '', + messages.commandsHint, + ].join('\n').trim(); + } + const progress = intent.projection.progress; + const state = progress <= 0 ? 'not-started' : progress >= 100 ? 'complete' : 'in-progress'; + const lines = [ + marker, + '', + `## ${messages.progress}: ${progress}% — ${messages.progressState[state]}`, + '', + `> **${messages.currentStatus}:** ${(0, github_comment_publication_policy_1.sanitizeAgentMarkdown)(intent.projection.summary, 1500).trim()}`, + ]; + if (progress < 100 && intent.projection.remaining?.trim()) { + lines.push('', `**${messages.next}:** ${(0, github_comment_publication_policy_1.sanitizeAgentMarkdown)(intent.projection.remaining, 3000).trim()}`); + } + else if (progress >= 100) { + lines.push('', messages.noActionRequired); + } + return lines.join('\n').trim(); +} +function planIntent(id, payload, locale) { + if (id !== 'RecommendStepsUseCase' + || !positiveInteger(payload.issueNumber) + || typeof payload.recommendedSteps !== 'string' + || !payload.recommendedSteps.trim()) + return undefined; + const state = (0, result_1.getResultPayload)(payload.recommendationState); + const issueFingerprint = typeof state?.issueDescriptionFingerprint === 'string' + ? state.issueDescriptionFingerprint + : (0, publication_identity_policy_1.createSemanticDigest)(payload.recommendedSteps); + const projection = Object.freeze({ + kind: 'plan', + recommendation: payload.recommendedSteps.trim(), + }); + return statusIntent('plan', payload.issueNumber, 'implementation', `issue-body:${safeDigest(issueFingerprint)}`, locale, projection); +} +function progressIntent(id, payload, locale) { + if (id !== 'CheckProgressUseCase' + || !positiveInteger(payload.issueNumber) + || typeof payload.progress !== 'number' + || typeof payload.summary !== 'string') + return undefined; + const progress = Math.max(0, Math.min(100, Math.round(payload.progress))); + const projection = Object.freeze({ + kind: 'progress', + progress, + summary: payload.summary.trim() || 'Progress was assessed without a summary.', + ...(typeof payload.remaining === 'string' && payload.remaining.trim() + ? { remaining: payload.remaining.trim() } + : {}), + ...(typeof payload.branch === 'string' && payload.branch.trim() ? { branch: payload.branch.trim() } : {}), + ...(typeof payload.developmentBranch === 'string' && payload.developmentBranch.trim() + ? { developmentBranch: payload.developmentBranch.trim() } + : {}), + }); + return statusIntent('progress', payload.issueNumber, 'work', `progress:${(0, publication_identity_policy_1.createSemanticDigest)(projection)}`, locale, projection); +} +function statusIntent(topic, issueNumber, key, sourceVersion, locale, projection) { + return Object.freeze({ + kind: 'status', + identity: Object.freeze({ topic, target: Object.freeze({ kind: 'issue', number: issueNumber }), key }), + sourceVersion, + digest: (0, publication_identity_policy_1.createSemanticDigest)({ locale, projection }), + locale, + projection, + }); +} +function positiveInteger(value) { + return typeof value === 'number' && Number.isSafeInteger(value) && value > 0; +} +function replyIntent(context, correlationId, messageKey, projection) { + return Object.freeze({ + kind: 'reply', + target: context.target, + correlationId, + messageKey, + locale: context.locale, + digest: (0, publication_identity_policy_1.createSemanticDigest)({ locale: context.locale, projection }), + projection, + }); +} +function safeMarkerToken(value) { + const trimmed = value.trim(); + return /^[A-Za-z0-9._:-]{1,128}$/u.test(trimmed) ? trimmed : `digest:${(0, publication_identity_policy_1.createSemanticDigest)(trimmed)}`; +} +function isStatusSnapshot(value) { + const record = (0, result_1.getResultPayload)(value); + return Boolean(record + && typeof record.owner === 'string' + && typeof record.repository === 'string' + && typeof record.event === 'string' + && typeof record.action === 'string' + && ['issue', 'pull-request', 'push', 'repository'].includes(String(record.target)) + && Array.isArray(record.issueLabels) + && Array.isArray(record.pullRequestLabels) + && typeof record.pullRequestDescriptionMode === 'string'); +} +function copyStatusSnapshot(snapshot) { + return Object.freeze({ + ...snapshot, + issueLabels: Object.freeze([...snapshot.issueLabels]), + pullRequestLabels: Object.freeze([...snapshot.pullRequestLabels]), + ...(snapshot.findingStates ? { findingStates: Object.freeze({ ...snapshot.findingStates }) } : {}), + }); +} +function safeDigest(value) { + return /^[a-f0-9]{8,64}$/u.test(value) ? value : (0, publication_identity_policy_1.createSemanticDigest)(value); +} + + /***/ }), /***/ 23381: @@ -57367,6 +45913,7 @@ exports.buildCopilotStatusResult = buildCopilotStatusResult; exports.formatCopilotStatus = formatCopilotStatus; const result_1 = __nccwpck_require__(73817); const bugbot_result_finding_state_projection_policy_1 = __nccwpck_require__(98117); +const locale_1 = __nccwpck_require__(15386); /** Builds a read-only status snapshot from the facts already loaded by setup. */ function buildCopilotStatusSnapshot(execution) { const issueLabels = [...(execution.labels?.currentIssueLabels ?? [])]; @@ -57429,7 +45976,9 @@ function buildCopilotStatusResult(snapshot, taskId) { payload: { status: snapshot }, }); } -function formatCopilotStatus(snapshot) { +function formatCopilotStatus(snapshot, locale = 'en-US') { + if ((0, locale_1.baseLanguage)(locale) === 'es') + return formatSpanishCopilotStatus(snapshot); const lines = [ '## Copilot status', `- **Repository:** ${snapshot.owner}/${snapshot.repository}`, @@ -57450,6 +45999,27 @@ function formatCopilotStatus(snapshot) { } return lines.join('\n'); } +function formatSpanishCopilotStatus(snapshot) { + const lines = [ + '## Estado de Copilot', + `- **Repositorio:** ${snapshot.owner}/${snapshot.repository}`, + `- **Destino:** ${snapshot.target}${snapshot.issueNumber ? ` #${snapshot.issueNumber}` : ''}${snapshot.pullRequestNumber ? ` / PR #${snapshot.pullRequestNumber}` : ''}`, + `- **Evento:** ${snapshot.event}${snapshot.action ? ` (${snapshot.action})` : ''}`, + `- **Rama:** ${snapshot.branch ?? 'desconocida'}`, + `- **Ciclo de vida:** ${snapshot.lifecycle ?? 'sin definir'}`, + `- **Esperando a:** ${snapshot.waitingFor ?? 'sin respuesta humana pendiente'}`, + `- **Política de descripción de PR:** ${snapshot.pullRequestDescriptionMode}`, + `- **Etiquetas de issue:** ${snapshot.issueLabels.length > 0 ? snapshot.issueLabels.join(', ') : 'ninguna'}`, + `- **Etiquetas de PR:** ${snapshot.pullRequestLabels.length > 0 ? snapshot.pullRequestLabels.join(', ') : 'ninguna'}`, + ]; + if (snapshot.findingStateEvidence === 'invalid') { + lines.push('- **Hallazgos de Bugbot:** evidencia no válida; revisa el resultado del workflow.'); + } + else if (snapshot.findingStates) { + lines.push(`- **Hallazgos de Bugbot:** ${snapshot.findingStates.open} abiertos, ${snapshot.findingStates.reopened} reabiertos, ${snapshot.findingStates.verificationRequired} requieren verificación, ${snapshot.findingStates.unknown} desconocidos, ${snapshot.findingStates.resolved} resueltos`); + } + return lines.join('\n'); +} /***/ }), @@ -58133,8 +46703,9 @@ const result_1 = __nccwpck_require__(73817); const issue_inactivity_1 = __nccwpck_require__(38572); const logging_ports_1 = __nccwpck_require__(6152); const application_error_1 = __nccwpck_require__(75999); +const locale_1 = __nccwpck_require__(15386); +const publication_identity_policy_1 = __nccwpck_require__(45403); const TASK_ID = 'CloseInactiveIssuesUseCase'; -const INACTIVITY_COMMENT = (thresholdHours) => `This issue was automatically closed due to inactivity while waiting for a response. No activity was detected for at least **${thresholdHours} hours**. Reopen it and add a comment if it still needs attention.`; /** Scans waiting issues and closes only candidates that remain inactive. */ async function runCloseInactiveIssuesWorkflow(param, dependencies) { const waitingLabels = unique([ @@ -58183,7 +46754,7 @@ async function runCloseInactiveIssuesWorkflow(param, dependencies) { continue; } closedCount++; - await dependencies.issueClosurePort.addComment(candidate.number, INACTIVITY_COMMENT(thresholdHours)); + await dependencies.issueClosurePort.addComment(candidate.number, buildInactivityExplanation(candidate, thresholdHours, param.locale)); (0, logging_ports_1.logInfo)(`Issue #${candidate.number} closed after inactivity.`); } catch (error) { @@ -58219,6 +46790,34 @@ async function runCloseInactiveIssuesWorkflow(param, dependencies) { })]; } } +function buildInactivityExplanation(candidate, thresholdHours, locale) { + const digest = (0, publication_identity_policy_1.createSemanticDigest)({ updatedAt: candidate.updatedAt, thresholdHours }); + const marker = (0, publication_identity_policy_1.buildPublicationMarker)({ + identity: { topic: 'inactivity', target: { kind: 'issue', number: candidate.number }, key: 'closure' }, + sourceVersion: `policy:${digest}`, + digest, + }); + if ((0, locale_1.baseLanguage)(locale) === 'es') { + return [ + marker, + '', + '## Issue cerrada por inactividad', + '', + `No se detectó actividad durante al menos **${thresholdHours} horas** mientras esta issue esperaba una respuesta.`, + '', + 'Si todavía necesita atención, vuelve a abrirla y añade un comentario con el contexto actualizado.', + ].join('\n'); + } + return [ + marker, + '', + '## Issue closed after inactivity', + '', + `No activity was detected for at least **${thresholdHours} hours** while this issue was waiting for a response.`, + '', + 'If it still needs attention, reopen it and add a comment with the current context.', + ].join('\n'); +} async function listCandidates(_param, waitingLabels, queryPort) { const candidates = []; for (const label of waitingLabels) { @@ -59518,6 +48117,7 @@ class ObserveBranchSyncUseCase { repository: context.repository.name, dependency, comparison, + locale: context.locale, }); if (latest && (0, branch_sync_notification_policy_1.isStaleBranchSyncComment)(latest.body)) { await this.notifications.updateComment(dependency.issueNumber, latest.id, comment); @@ -59528,7 +48128,7 @@ class ObserveBranchSyncUseCase { return success(dependency, comparison.behindBy, "stale"); } if (latest && (0, branch_sync_notification_policy_1.isStaleBranchSyncComment)(latest.body)) { - await this.notifications.updateComment(dependency.issueNumber, latest.id, (0, branch_sync_notification_policy_1.buildAlignedBranchSyncComment)(dependency)); + await this.notifications.updateComment(dependency.issueNumber, latest.id, (0, branch_sync_notification_policy_1.buildAlignedBranchSyncComment)(dependency, context.locale)); } return success(dependency, 0, "aligned"); } @@ -60897,6 +49497,7 @@ function runHelpCommand(param, options) { executed: true, stepFormat: 'markdown', steps: [(0, copilot_interaction_policy_1.buildCopilotHelpMessage)(param.trustedBotLogin)], + payload: Object.freeze({ publication: Object.freeze({ kind: 'help', botLogin: param.trustedBotLogin }) }), })]; } async function runDescriptionCommand(param, options, actorAuthorizationPort) { @@ -62521,6 +51122,7 @@ function projectInactivityContext(source) { ]), activityLabel: source.labels.lifecycle.aiProcessing, thresholdHours: source.inactivityThresholdHours, + locale: source.locale?.issue ?? 'en-US', }); } function projectBranchObservationContext(source) { @@ -62529,6 +51131,7 @@ function projectBranchObservationContext(source) { deletedPush: typeof source.inputs?.after === 'string' && /^0+$/u.test(source.inputs.after), ...(source.tokenUser ? { trustedBotLogin: source.tokenUser } : {}), repository: Object.freeze({ owner: source.owner, name: source.repo }), + locale: source.locale?.issue ?? 'en-US', }); } function projectUserRequestContext(source) { @@ -65520,7 +54123,6 @@ function isRecord(value) { */ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.publishFindings = publishFindings; -const comment_watermark_1 = __nccwpck_require__(23623); const finding_1 = __nccwpck_require__(31011); const publish_issue_finding_comment_1 = __nccwpck_require__(84950); const publish_pr_review_comments_1 = __nccwpck_require__(50352); @@ -65528,16 +54130,12 @@ const publish_overflow_comment_1 = __nccwpck_require__(10974); async function publishFindings(param) { const { operation, context, findings, commitSha, overflowCount = 0, overflowTitles = [], ports } = param; const { existingByFindingId, canonicalPullRequest, prContext } = context; - const watermark = commitSha - ? (0, comment_watermark_1.getCommentWatermark)({ commitSha, owner: operation.repository.owner, repo: operation.repository.name }) - : (0, comment_watermark_1.getCommentWatermark)(); const reviewPublisher = prContext && canonicalPullRequest ? new publish_pr_review_comments_1.PullRequestReviewCommentPublisher({ repository: ports.pullRequestComments, operation, openPrNumber: canonicalPullRequest.number, prContext, - watermark, ruleSources: context.reviewRuleSources, omittedRuleCount: context.omittedReviewRules, }) @@ -65639,7 +54237,7 @@ class PullRequestReviewCommentPublisher { } // Existing comments do not carry enough anchor metadata to prove that a // GitHub suggestion is still attached to a RIGHT-side changed line. - const body = `${(0, bugbot_finding_marker_policy_1.buildCommentBody)(finding, false, undefined, { includeSuggestedChange: false })}\n\n${this.options.watermark}`; + const body = (0, bugbot_finding_marker_policy_1.buildCommentBody)(finding, false, undefined, { includeSuggestedChange: false }); await this.options.repository.updatePullRequestReviewComment(existing.pullRequest.commentIdentity, body); if (existing.pullRequest.resolved || existing.pullRequest.threadResolved === true) { // Persist the open marker before reopening the native thread. This @@ -65653,7 +54251,7 @@ class PullRequestReviewCommentPublisher { const findingBody = (0, bugbot_finding_marker_policy_1.buildCommentBody)(finding, false, undefined, { includeSuggestedChange: allowSuggestedChanges && anchor?.subjectType === 'line' && anchor.side === 'RIGHT', }); - const body = `${findingBody}\n\n${this.options.watermark}`; + const body = findingBody; this.findingsToCreate.push(finding); if (!anchor) { this.unanchoredBodies.push(findingBody); @@ -65680,7 +54278,7 @@ class PullRequestReviewCommentPublisher { if (this.findingsToCreate.length === 0 && overflowCount === 0) return; const { repository, operation, openPrNumber, prContext } = this.options; - await repository.createReviewWithComments(openPrNumber, prContext.prHeadSha, buildReviewSummary(this.findingsToCreate, this.commentsToCreate.length, this.unanchoredBodies, overflowCount, overflowTitles, this.options.watermark, operation.analysis.reviewConfiguration.traceRules + await repository.createReviewWithComments(openPrNumber, prContext.prHeadSha, buildReviewSummary(this.findingsToCreate, this.commentsToCreate.length, this.unanchoredBodies, overflowCount, overflowTitles, operation.analysis.reviewConfiguration.traceRules ? this.options.ruleSources ?? [] : [], operation.analysis.reviewConfiguration.traceRules ? this.options.omittedRuleCount ?? 0 @@ -65719,7 +54317,7 @@ function resolveReviewAnchor(reportedLine, reportedEndLine, reportedPath, contex const fallback = context.prFiles.find((file) => file.status !== 'removed') ?? context.prFiles[0]; return fallback ? { path: fallback.filename, subjectType: 'file' } : undefined; } -function buildReviewSummary(findings, inlineCount, unanchoredBodies, overflowCount, overflowTitles, watermark, ruleSources = [], omittedRuleCount = 0, analyzedHeadSha = 'unknown', locale = 'en-US') { +function buildReviewSummary(findings, inlineCount, unanchoredBodies, overflowCount, overflowTitles, ruleSources = [], omittedRuleCount = 0, analyzedHeadSha = 'unknown', locale = 'en-US') { const findingLines = findings.map((finding) => { const severity = sanitizeSummaryText(finding.severity, 32) || "unspecified"; const title = sanitizeSummaryText(finding.title, 500) || 'Potential problem'; @@ -65755,7 +54353,6 @@ function buildReviewSummary(findings, inlineCount, unanchoredBodies, overflowCou rows.push(`| — | ${omittedRuleCount} omitted by duplicate, empty, or combined-budget policy |`); sections.push(`### Review configuration\n\nRules in effective precedence order:\n\n| Source | Status |\n| --- | --- |\n${rows.join('\n')}`); } - sections.push(watermark); return sections.join("\n\n"); } function sanitizeSummaryText(value, maximum) { @@ -66189,6 +54786,8 @@ exports.synchronizeBugbotReviewPresentation = synchronizeBugbotReviewPresentatio const bugbot_review_presentation_policy_1 = __nccwpck_require__(43799); const bugbot_review_ownership_policy_1 = __nccwpck_require__(83288); const review_projection_1 = __nccwpck_require__(80859); +const publication_identity_policy_1 = __nccwpck_require__(45403); +const publication_message_catalog_1 = __nccwpck_require__(34223); const MAX_REVIEW_UPDATES_PER_RUN = 20; const REVIEW_UPDATE_CONCURRENCY = 4; /** @@ -66271,10 +54870,13 @@ async function synchronizeStatusCard(input, projection, navigation) { failed = true; } const duplicateResults = await mapWithConcurrency(trustedStatusComments.slice(1), REVIEW_UPDATE_CONCURRENCY, async (duplicate) => { + const messages = (0, publication_message_catalog_1.resolveStaticPublicationCatalog)(input.target.locale).catalog; await input.ports.comments.updateComment(input.target.pullRequestNumber, duplicate.id, [ - '## 🤖 Bugbot status moved', + (0, publication_identity_policy_1.buildDuplicateMarker)(canonical?.id ?? duplicate.id), + '', + messages.supersededStatus, '', - `This duplicate status card is no longer current. [Use the canonical PR status](${navigation.pullRequestUrl}).`, + `[${messages.viewCurrentStatus}](${navigation.pullRequestUrl}).`, ].join('\n'), { commitSha: input.snapshot.verifiedHeadSha }); }); if (duplicateResults.includes('rejected')) @@ -66637,81 +55239,6 @@ function logSize(size, githubSize, reason, currentLabel) { } -/***/ }), - -/***/ 90762: -/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.buildCommitNotificationContent = buildCommitNotificationContent; -const list_utils_1 = __nccwpck_require__(42277); -const SEPARATOR = "------------------------------------------------------"; -function buildCommitNotificationContent(param, commitPrefix) { - const theme = resolveTheme(param); - let body = ` -# ${theme.title} - -**Changes on branch \`${param.branch}\`:** - -`; - let shouldWarn = false; - for (const commit of param.commits) { - const commitMessage = commit.message ?? ""; - body += ` -${SEPARATOR} - -- ${commit.id ?? "unknown"} by **${commit.author?.name ?? "unknown"}** (@${commit.author?.username ?? "unknown"}) -\`\`\` -${commitMessage.split(`${commitPrefix}: `).join("")} -\`\`\` - -`; - if (hasUnexpectedPrefix(commitMessage, commitPrefix)) - shouldWarn = true; - } - if (shouldWarn && commitPrefix.length > 0) { - body += ` -${SEPARATOR} -## ⚠️ Attention - -One or more commits didn't start with the prefix **${commitPrefix}**. - -\`\`\` -${commitPrefix}: created hello-world app -\`\`\` -`; - } - if (theme.image && param.imagesOnCommit) { - body += ` -${SEPARATOR} - -![image](${theme.image}) -`; - } - return { body, shouldWarn }; -} -function resolveTheme(param) { - const titles = { - release: '🚀 Release News', - hotfix: '🔥🐛 Hotfix News', - bugfix: '🐛 Bugfix News', - feature: '✨ Feature News', - docs: '📝 Documentation News', - chore: '🔧 Chore News', - automatic: '🪄 Automatic News', - }; - return { title: titles[param.theme], image: (0, list_utils_1.getRandomElement)([...param.themeImages]) }; -} -function hasUnexpectedPrefix(commitMessage, commitPrefix) { - return commitPrefix.length > 0 - && !commitMessage.startsWith(commitPrefix) - && !commitMessage.startsWith("Merge branch ") - && !commitMessage.startsWith("gh-action: "); -} - - /***/ }), /***/ 6287: @@ -67152,35 +55679,25 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.runNotifyNewCommitOnIssueWorkflow = runNotifyNewCommitOnIssueWorkflow; const result_1 = __nccwpck_require__(73817); const logging_ports_1 = __nccwpck_require__(6152); -const execute_script_use_case_1 = __nccwpck_require__(65440); -const commit_notification_content_policy_1 = __nccwpck_require__(90762); const application_error_1 = __nccwpck_require__(75999); async function runNotifyNewCommitOnIssueWorkflow(param, taskId, issueRepository) { const result = []; try { - const branchName = param.branch; - let commitPrefix = ""; - if (param.commitPrefixBuilder.length > 0) { - commitPrefix = (0, execute_script_use_case_1.buildCommitPrefix)(branchName, param.commitPrefixBuilder); - (0, logging_ports_1.logDebugInfo)(`Commit prefix: ${commitPrefix}`); - } - const { body } = (0, commit_notification_content_policy_1.buildCommitNotificationContent)(param, commitPrefix); if (param.reopenOnPush) { const opened = await issueRepository.openIssue(param.issueNumber); - if (opened) { - await issueRepository.addComment(param.issueNumber, `This issue was re-opened after pushing new commits to the branch \`${branchName}\`.`); - } + (0, logging_ports_1.logInfo)(opened + ? `Issue #${param.issueNumber} reopened after a push; native state is sufficient.` + : `Issue #${param.issueNumber} was already open; no conversation notification was created.`); } - await issueRepository.addComment(param.issueNumber, body); } catch (error) { - const semanticError = (0, application_error_1.toApplicationError)(error, 'provider.unavailable', 'Unable to notify the issue about the new commit.'); + const semanticError = (0, application_error_1.toApplicationError)(error, 'provider.unavailable', 'Unable to synchronize the issue state after the new commit.'); (0, logging_ports_1.logError)(semanticError); result.push(new result_1.Result({ id: taskId, success: false, executed: true, - steps: ["Tried to notify the new commit on the issue, but there was a problem."], + steps: ["Tried to synchronize issue state after the new commit, but there was a problem."], errors: [semanticError], })); } @@ -68094,14 +56611,13 @@ const logging_ports_1 = __nccwpck_require__(6152); const task_emoji_1 = __nccwpck_require__(46103); const publish_resume_workflow_1 = __nccwpck_require__(55340); class PublishResultUseCase { - constructor(issueNotificationPort, logReport) { - this.issueNotificationPort = issueNotificationPort; - this.logReport = logReport; + constructor(comments) { + this.comments = comments; this.taskId = 'PublishResultUseCase'; } async invoke(param) { (0, logging_ports_1.logInfo)(`${(0, task_emoji_1.getTaskEmoji)(this.taskId)} Executing ${this.taskId}.`); - return (0, publish_resume_workflow_1.runPublishResume)(param, this.taskId, this.issueNotificationPort, this.logReport); + return (0, publish_resume_workflow_1.runPublishResume)(param, this.taskId, this.comments); } } exports.PublishResultUseCase = PublishResultUseCase; @@ -68118,131 +56634,240 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.projectPublishResultContext = projectPublishResultContext; exports.runPublishResume = runPublishResume; const result_1 = __nccwpck_require__(73817); -const list_utils_1 = __nccwpck_require__(42277); const logging_ports_1 = __nccwpck_require__(6152); -const result_publication_policy_1 = __nccwpck_require__(71512); -const application_error_presentation_policy_1 = __nccwpck_require__(95067); +const semantic_result_publication_policy_1 = __nccwpck_require__(81985); const application_error_1 = __nccwpck_require__(75999); +const publication_identity_policy_1 = __nccwpck_require__(45403); +const reply_publication_workflow_1 = __nccwpck_require__(22824); +const status_card_publication_workflow_1 = __nccwpck_require__(62963); function projectPublishResultContext(source) { - const results = Object.freeze(source.currentConfiguration.results.map(projectResult)); + const target = publicationTarget(source); return Object.freeze({ - genericCommentMode: resolveGenericCommentMode(source, results), - debug: source.debug, - target: Object.freeze({ - isSingleAction: source.isSingleAction, - singleActionIssue: source.singleAction.issue, - isIssue: source.isIssue, - issueNumber: source.issue.number, - isPullRequest: source.isPullRequest, - pullRequestNumber: source.pullRequest.number, - isPush: source.isPush, - pushIssueNumber: source.issueNumber, - }), - presentation: Object.freeze({ - isIssue: source.isIssue, - isPullRequest: source.isPullRequest, - issueNotBranched: source.issueNotBranched, - releaseActive: source.release.active, - hotfixActive: source.hotfix.active, - isBugfix: source.isBugfix, - isFeature: source.isFeature, - isDocs: source.isDocs, - isChore: source.isChore, - images: projectImages(source.images), - }), - results, + owner: source.owner, + repository: source.repo, + botLogin: source.tokenUser?.trim() ?? '', + locale: source.isPullRequest + ? source.locale?.pullRequest ?? 'en-US' + : source.locale?.issue ?? 'en-US', + ...(target ? { target } : {}), + requestCorrelationId: requestCorrelationId(source, target), + results: Object.freeze(source.currentConfiguration.results.map(copyResult)), }); } -async function runPublishResume(param, taskId, issueNotificationPort, logReport) { +/** + * Compatibility boundary for legacy Result producers. Only explicitly mapped + * semantic payloads may reach GitHub; steps, reminders, errors, images, and + * debug logs remain operator evidence in the Job Summary and logs. + */ +async function runPublishResume(param, taskId, comments) { try { - if (param.genericCommentMode !== 'publish') { - (0, logging_ports_1.logInfo)(`Generic result comment omitted: ${param.genericCommentMode}.`); + const semanticContext = { + locale: param.locale, + results: param.results, + ...(param.target ? { target: param.target } : {}), + correlationId: param.requestCorrelationId, + botLogin: param.botLogin, + }; + const replies = (0, semantic_result_publication_policy_1.selectSemanticReplyIntents)(semanticContext); + const statuses = (0, semantic_result_publication_policy_1.selectSemanticStatusIntents)(semanticContext); + if (replies.length === 0 && statuses.length === 0) { + (0, logging_ports_1.logInfo)('Conversation publication omitted: no explicit semantic publication intent.'); return undefined; } - const sections = (0, result_publication_policy_1.renderResultSections)(param.results); - const debugLogSection = (0, result_publication_policy_1.buildDebugLogSection)(param.debug, logReport.getAccumulatedLogsAsText()); - if (!(0, result_publication_policy_1.hasPublishableContent)(sections, debugLogSection)) + if (!param.botLogin) { + (0, logging_ports_1.logInfo)('Conversation publication omitted: the configured bot identity is unavailable.'); return undefined; - const issueNumber = (0, result_publication_policy_1.resolveResultPublicationIssueNumber)(param.target); - if (issueNumber === undefined) - return undefined; - await issueNotificationPort.addComment(issueNumber, buildResumeComment(param, sections, debugLogSection)); + } + for (const intent of replies) { + const outcome = await (0, reply_publication_workflow_1.reconcileReply)({ + owner: param.owner, + repository: param.repository, + botLogin: param.botLogin, + intent, + }, comments); + (0, logging_ports_1.logInfo)(`Semantic ${intent.messageKey} reply ${outcome.effect}; duplicates compacted=${outcome.duplicatesCompacted}.`); + } + for (const intent of statuses) { + const outcome = await (0, status_card_publication_workflow_1.reconcileStatusCard)({ + owner: param.owner, + repository: param.repository, + botLogin: param.botLogin, + intent, + }, comments); + (0, logging_ports_1.logInfo)(`Semantic ${intent.identity.topic} publication ${outcome.effect}; duplicates compacted=${outcome.duplicatesCompacted}.`); + } return undefined; } catch (error) { - const semanticError = (0, application_error_1.toApplicationError)(error, 'provider.unavailable', 'Unable to publish the workflow summary.'); + const semanticError = (0, application_error_1.toApplicationError)(error, 'provider.unavailable', 'Unable to publish semantic GitHub status.'); (0, logging_ports_1.logError)(semanticError); - return new result_1.Result({ - id: taskId, - success: false, - executed: true, - steps: ['Tried to publish the resume, but there was a problem.'], - errors: [semanticError], - }); + return new result_1.Result({ id: taskId, success: false, executed: true, errors: [semanticError] }); } } -function resolveGenericCommentMode(source, results) { - if (!source.isPullRequest || source.isSingleAction) - return 'publish'; - if (source.pullRequest.action === 'edited') - return 'omit-metadata-only'; - return results.some((result) => result.id === 'DetectPotentialProblemsUseCase' && result.executed) - ? 'omit-feature-owned' - : 'publish'; +function publicationTarget(source) { + const pullRequestNumber = source.pullRequest?.number; + if (source.isPullRequest && positiveInteger(pullRequestNumber)) { + return Object.freeze({ kind: 'pull-request', number: pullRequestNumber }); + } + const issueNumber = source.issue?.number ?? source.issueNumber; + return positiveInteger(issueNumber) ? Object.freeze({ kind: 'issue', number: issueNumber }) : undefined; } -function buildResumeComment(param, sections, debugLogSection) { - const presentation = (0, result_publication_policy_1.resolveResultPublicationPresentation)(param.presentation, (images) => (0, list_utils_1.getRandomElement)([...images])); - const imageMarkdown = shouldRenderImage(param.presentation, presentation.image) - ? `![image](${presentation.image})` - : ''; - return `# ${presentation.title} -${sections.content} -${sections.errors} - -${imageMarkdown} - -${sections.footer} -${debugLogSection} -🚀 Happy coding! - `; +function requestCorrelationId(source, target) { + const commentId = source.inputs?.comment?.id; + if (positiveInteger(commentId)) + return `comment:${commentId}`; + return `event:${(0, publication_identity_policy_1.createSemanticDigest)({ + eventName: source.eventName ?? 'unknown', + action: source.inputs?.action ?? '', + target: target ?? null, + })}`; } -function shouldRenderImage(context, image) { - if (!image) - return false; - return context.isIssue - ? context.images.imagesOnIssue - : context.images.imagesOnPullRequest; +function positiveInteger(value) { + return typeof value === 'number' && Number.isSafeInteger(value) && value > 0; } -function projectResult(result) { - return Object.freeze({ +function copyResult(result) { + return Object.freeze(new result_1.Result({ id: result.id, + success: result.success, executed: result.executed, - steps: Object.freeze([...result.steps]), - reminders: Object.freeze([...result.reminders]), - errors: Object.freeze(result.errors.map((error) => Object.freeze((0, application_error_presentation_policy_1.buildApplicationErrorPresentation)(error)))), + steps: [...result.steps], + payload: copyPayload(result.payload), + reminders: [...result.reminders], + errors: [...result.errors], stepFormat: result.stepFormat, + })); +} +function copyPayload(payload) { + if (Array.isArray(payload)) + return Object.freeze(payload.map(copyPayload)); + if (payload && typeof payload === 'object') { + return Object.freeze(Object.fromEntries(Object.entries(payload).map(([key, value]) => [key, copyPayload(value)]))); + } + return payload; +} + + +/***/ }), + +/***/ 22824: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.reconcileReply = reconcileReply; +const github_publication_1 = __nccwpck_require__(35793); +const github_user_policy_1 = __nccwpck_require__(84403); +const publication_identity_policy_1 = __nccwpck_require__(45403); +const publication_message_catalog_1 = __nccwpck_require__(34223); +const semantic_result_publication_policy_1 = __nccwpck_require__(81985); +/** Publishes an explicit reply at most once for a trusted request correlation. */ +async function reconcileReply(context, comments) { + if (!context.botLogin.trim()) + return Object.freeze({ effect: 'unchanged', duplicatesCompacted: 0 }); + const target = context.intent.target; + let owned = matchingReplies(await comments.listIssueComments(target.number), context); + let effect = 'unchanged'; + if (owned.length === 0) { + await comments.addComment(target.number, (0, semantic_result_publication_policy_1.renderSemanticReply)(context.intent)); + effect = 'created'; + owned = matchingReplies(await comments.listIssueComments(target.number), context); + } + if (owned.length === 0) + return Object.freeze({ effect, duplicatesCompacted: 0 }); + const [canonical, ...duplicates] = owned.sort((left, right) => left.id - right.id); + for (const duplicate of duplicates) { + await comments.updateComment(target.number, duplicate.id, duplicatePointer(context, canonical.id)); + } + return Object.freeze({ effect, canonicalCommentId: canonical.id, duplicatesCompacted: duplicates.length }); +} +function matchingReplies(comments, context) { + const expectedTarget = (0, github_publication_1.publicationTargetToken)(context.intent.target); + return comments.filter(comment => { + const marker = (0, publication_identity_policy_1.parsePublicationReplyMarker)(comment.body); + return marker?.target === expectedTarget + && marker.correlationId === context.intent.correlationId + && marker.messageKey === context.intent.messageKey + && (0, github_user_policy_1.githubUsersMatch)(comment.user?.login ?? '', context.botLogin); }); } -function projectImages(images) { +function duplicatePointer(context, canonicalCommentId) { + const messages = (0, publication_message_catalog_1.resolveStaticPublicationCatalog)(context.intent.locale).catalog; + const targetPath = context.intent.target.kind === 'pull-request' ? 'pull' : 'issues'; + const url = `https://github.com/${encodeURIComponent(context.owner)}/${encodeURIComponent(context.repository)}/${targetPath}/${context.intent.target.number}#issuecomment-${canonicalCommentId}`; + return [ + (0, publication_identity_policy_1.buildDuplicateMarker)(canonicalCommentId), + '', + messages.duplicateReply, + '', + `[${messages.viewOriginalResponse}](${url}).`, + ].join('\n'); +} + + +/***/ }), + +/***/ 62963: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.reconcileStatusCard = reconcileStatusCard; +const github_publication_1 = __nccwpck_require__(35793); +const github_user_policy_1 = __nccwpck_require__(84403); +const publication_identity_policy_1 = __nccwpck_require__(45403); +const publication_message_catalog_1 = __nccwpck_require__(34223); +const semantic_result_publication_policy_1 = __nccwpck_require__(81985); +async function reconcileStatusCard(context, comments) { + if (!context.botLogin.trim()) + return Object.freeze({ effect: 'unchanged', duplicatesCompacted: 0 }); + const target = context.intent.identity.target; + const rendered = (0, semantic_result_publication_policy_1.renderSemanticStatus)(context.intent); + let owned = ownedCards(await comments.listIssueComments(target.number), context.intent.identity, context.botLogin); + let effect = 'unchanged'; + if (owned.length === 0) { + await comments.addComment(target.number, rendered); + effect = 'created'; + owned = ownedCards(await comments.listIssueComments(target.number), context.intent.identity, context.botLogin); + } + if (owned.length === 0) + return Object.freeze({ effect, duplicatesCompacted: 0 }); + const [canonical, ...duplicates] = owned.sort((left, right) => left.id - right.id); + const canonicalMarker = (0, publication_identity_policy_1.parsePublicationMarker)(canonical.body); + if (canonicalMarker?.digest !== context.intent.digest) { + await comments.updateComment(target.number, canonical.id, rendered); + effect = effect === 'created' ? 'created' : 'updated'; + } + for (const duplicate of duplicates) { + await comments.updateComment(target.number, duplicate.id, duplicatePointer(context, canonical.id)); + } return Object.freeze({ - imagesOnIssue: images.imagesOnIssue, - issueAutomaticActions: Object.freeze([...images.issueAutomaticActions]), - issueFeatureGifs: Object.freeze([...images.issueFeatureGifs]), - issueBugfixGifs: Object.freeze([...images.issueBugfixGifs]), - issueReleaseGifs: Object.freeze([...images.issueReleaseGifs]), - issueHotfixGifs: Object.freeze([...images.issueHotfixGifs]), - issueDocsGifs: Object.freeze([...images.issueDocsGifs]), - issueChoreGifs: Object.freeze([...images.issueChoreGifs]), - imagesOnPullRequest: images.imagesOnPullRequest, - pullRequestAutomaticActions: Object.freeze([...images.pullRequestAutomaticActions]), - pullRequestFeatureGifs: Object.freeze([...images.pullRequestFeatureGifs]), - pullRequestBugfixGifs: Object.freeze([...images.pullRequestBugfixGifs]), - pullRequestReleaseGifs: Object.freeze([...images.pullRequestReleaseGifs]), - pullRequestHotfixGifs: Object.freeze([...images.pullRequestHotfixGifs]), - pullRequestDocsGifs: Object.freeze([...images.pullRequestDocsGifs]), - pullRequestChoreGifs: Object.freeze([...images.pullRequestChoreGifs]), + effect, + canonicalCommentId: canonical.id, + duplicatesCompacted: duplicates.length, }); } +function ownedCards(comments, identity, botLogin) { + return comments.filter(comment => { + const marker = (0, publication_identity_policy_1.parsePublicationMarker)(comment.body); + return marker !== undefined + && (0, github_publication_1.publicationIdentityEquals)(marker.identity, identity) + && (0, github_user_policy_1.githubUsersMatch)(comment.user?.login ?? '', botLogin); + }); +} +function duplicatePointer(context, canonicalCommentId) { + const messages = (0, publication_message_catalog_1.resolveStaticPublicationCatalog)(context.intent.locale).catalog; + const targetPath = context.intent.identity.target.kind === 'pull-request' ? 'pull' : 'issues'; + const url = `https://github.com/${encodeURIComponent(context.owner)}/${encodeURIComponent(context.repository)}/${targetPath}/${context.intent.identity.target.number}#issuecomment-${canonicalCommentId}`; + return [ + (0, publication_identity_policy_1.buildDuplicateMarker)(canonicalCommentId), + '', + messages.supersededStatus, + '', + `[${messages.viewCurrentStatus}](${url}).`, + ].join('\n'); +} /***/ }), @@ -69189,7 +57814,6 @@ class CloseIssueAfterMergingUseCase { const closed = await this.issueRepository.closeIssue(param.issueNumber); if (closed) { (0, logging_ports_1.logInfo)(`Issue #${param.issueNumber} closed after merging PR #${param.pullRequestNumber}.`); - await this.issueRepository.addComment(param.issueNumber, `This issue was closed after merging #${param.pullRequestNumber}.`); result.push(new result_1.Result({ id: this.taskId, success: true, @@ -69251,15 +57875,13 @@ class CloseNotAllowedIssueUseCase { try { const closed = await this.issueRepository.closeIssue(param.issueNumber); if (closed) { - (0, logging_ports_1.logInfo)(`Issue #${param.issueNumber} closed (author not allowed). Adding comment.`); - await this.issueRepository.addComment(param.issueNumber, `This issue has been closed because the author is not a member of the project. The user may be banned if the fact is repeated.`); + (0, logging_ports_1.logInfo)(`Issue #${param.issueNumber} closed (author not allowed).`); result.push(new result_1.Result({ id: this.taskId, success: true, executed: true, - steps: [ - `#${param.issueNumber} was automatically closed because the author is not a member of the project.` - ] + steps: [`#${param.issueNumber} was automatically closed because the author is not a member of the project.`], + payload: Object.freeze({ publication: Object.freeze({ kind: 'access-policy' }) }), })); } else { @@ -76131,7 +64753,6 @@ exports.IssueClosureRepository = IssueClosureRepository; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.IssueContentRepository = void 0; -const comment_watermark_1 = __nccwpck_require__(23623); const comment_content_policy_1 = __nccwpck_require__(77454); const logger_1 = __nccwpck_require__(91151); const github_pagination_policy_1 = __nccwpck_require__(44812); @@ -76186,13 +64807,13 @@ class IssueContentRepository { (0, logger_1.logDebugInfo)(`Skipped empty comment publication for Issue ${issueNumber}.`); return; } - const watermark = (0, comment_watermark_1.getCommentWatermark)(options?.commitSha ? { commitSha: options.commitSha, owner, repo: repository } : undefined); + void options; const octokit = this.githubClient.getClient(token); await octokit.rest.issues.createComment({ owner, repo: repository, issue_number: issueNumber, - body: `${comment}\n\n${watermark}`, + body: comment, }); (0, logger_1.logDebugInfo)(`Comment added to Issue ${issueNumber}.`); }; @@ -76201,13 +64822,13 @@ class IssueContentRepository { (0, logger_1.logDebugInfo)(`Skipped empty comment update for Issue ${issueNumber}.`); return; } - const watermark = (0, comment_watermark_1.getCommentWatermark)(options?.commitSha ? { commitSha: options.commitSha, owner, repo: repository } : undefined); + void options; const octokit = this.githubClient.getClient(token); await octokit.rest.issues.updateComment({ owner, repo: repository, comment_id: commentId, - body: `${comment}\n\n${watermark}`, + body: comment, }); (0, logger_1.logDebugInfo)(`Comment ${commentId} updated in Issue ${issueNumber}.`); }; @@ -81067,6 +69688,37 @@ function isPullRequestConversationComment(input) { } +/***/ }), + +/***/ 35793: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.PUBLICATION_TOPICS = void 0; +exports.publicationIdentityEquals = publicationIdentityEquals; +exports.publicationTargetToken = publicationTargetToken; +exports.PUBLICATION_TOPICS = [ + 'plan', + 'progress', + 'branch-sync', + 'bugbot', + 'release', + 'inactivity', + 'access-policy', +]; +function publicationIdentityEquals(left, right) { + return left.topic === right.topic + && left.target.kind === right.target.kind + && left.target.number === right.target.number + && left.key === right.key; +} +function publicationTargetToken(target) { + return `${target.kind === 'pull-request' ? 'pr' : 'issue'}:${target.number}`; +} + + /***/ }), /***/ 84403: @@ -82790,14 +71442,13 @@ function createIssueUseCaseCompositionRoot(binding) { const projectContent = (0, shared_capability_port_binding_1.bindProjectContent)(issueMetadata, projectBoard.command, projectBoard.link, binding); const boundProjectBoard = (0, lifecycle_capability_port_binding_1.bindProjectBoardCommands)(projectBoard.command, binding); const boundBranchLifecycle = (0, lifecycle_capability_port_binding_1.bindBranchLifecycle)(branchLifecycle, binding); - const boundIssueClosure = (0, lifecycle_capability_port_binding_1.bindIssueClosure)(issueClosure, binding); const boundIssueAssignee = (0, lifecycle_capability_port_binding_1.bindIssueAssignee)(issueAssignee, binding); const boundOrganizationMembers = (0, lifecycle_capability_port_binding_1.bindOrganizationMemberSelection)(organizationMembers, binding); const boundLinkedBranch = (0, lifecycle_capability_port_binding_1.bindLinkedBranchCommand)(linkedBranch, binding); const moveIssueToInProgress = new move_issue_to_in_progress_1.MoveIssueToInProgressUseCase(boundProjectBoard); const workflowSteps = { checkPermissions: new check_permissions_use_case_1.CheckPermissionsUseCase((0, shared_capability_port_binding_1.bindOrganizationMembers)(organizationMembers, binding)), - closeNotAllowedIssue: new close_not_allowed_issue_use_case_1.CloseNotAllowedIssueUseCase(boundIssueClosure), + closeNotAllowedIssue: new close_not_allowed_issue_use_case_1.CloseNotAllowedIssueUseCase((0, lifecycle_capability_port_binding_1.bindIssueState)(issueClosure, binding)), removeIssueBranches: new remove_issue_branches_use_case_1.RemoveIssueBranchesUseCase(boundBranchLifecycle), assignMemberToIssue: new assign_members_to_issue_use_case_1.AssignMemberToIssueUseCase(boundIssueAssignee, boundOrganizationMembers), updateTitle: new update_title_use_case_1.UpdateTitleUseCase((0, shared_capability_port_binding_1.bindIssueTitle)(issueTitle, binding)), @@ -82825,6 +71476,7 @@ exports.bindIssueAssignee = bindIssueAssignee; exports.bindOrganizationMemberSelection = bindOrganizationMemberSelection; exports.bindPullRequestReviewer = bindPullRequestReviewer; exports.bindIssueClosure = bindIssueClosure; +exports.bindIssueState = bindIssueState; exports.bindIssueTypeAssignment = bindIssueTypeAssignment; exports.bindProjectBoardCommands = bindProjectBoardCommands; exports.bindBranchLifecycle = bindBranchLifecycle; @@ -82864,6 +71516,11 @@ function bindIssueClosure(port, binding) { addComment: (issueNumber, comment) => port.addComment(binding.owner, binding.repository, issueNumber, comment, binding.token), }); } +function bindIssueState(port, binding) { + return Object.freeze({ + closeIssue: (issueNumber) => port.closeIssue(binding.owner, binding.repository, issueNumber, binding.token), + }); +} function bindIssueTypeAssignment(port, binding) { return Object.freeze({ setIssueType: (issueNumber, issueType) => port.setIssueType(binding.owner, binding.repository, issueNumber, issueType, binding.token), @@ -83083,7 +71740,7 @@ function createPullRequestReviewCommentUseCaseCompositionRoot(binding) { return new pull_request_review_comment_use_case_1.PullRequestReviewCommentUseCase(new check_pull_request_comment_language_use_case_1.CheckPullRequestCommentLanguageUseCase(new comment_language_translation_workflow_1.CommentLanguageTranslationWorkflow(language)), new detect_bugbot_fix_intent_use_case_1.DetectBugbotFixIntentUseCase(findings, bugbot.scm.context), new think_use_case_1.ThinkUseCase((0, shared_capability_port_binding_1.bindIssueDescriptionQuery)((0, issue_content_composition_root_1.createIssueContentCompositionRoot)(), binding), (0, shared_capability_port_binding_1.bindIssueNotification)((0, issue_interaction_composition_root_1.createIssueNotificationRepository)(), binding), findings), new bugbot_autofix_use_case_1.BugbotAutofixUseCase(fixer, bugbot.scm.context, bugbotGit), new user_request_use_case_1.DoUserRequestUseCase(fixer, bugbotGit), (0, actor_authorization_composition_root_1.createActorAuthorizationRepository)(), bugbotGit, new dismiss_bugbot_findings_use_case_1.DismissBugbotFindingsUseCase({ contextPorts: bugbot.scm.context, resolutionPorts: bugbot.scm.resolution }), new detect_potential_problems_use_case_1.DetectPotentialProblemsUseCase(findings, bugbot.scm, bugbot.telemetry), pullRequestDescription, new remember_bugbot_rule_use_case_1.RememberBugbotRuleUseCase(bugbot.rules), branchSync); } function createCommitUseCaseCompositionRoot(projectBoardCommandPort, binding) { - return new commit_use_case_1.CommitUseCase(new notify_new_commit_on_issue_use_case_1.NotifyNewCommitOnIssueUseCase((0, push_single_action_capability_port_binding_1.bindIssuePushNotification)((0, issue_interaction_composition_root_1.createIssueNotificationRepository)(), binding)), new check_changes_issue_size_use_case_1.CheckChangesIssueSizeUseCase((0, lifecycle_capability_port_binding_2.bindProjectBoardCommands)(projectBoardCommandPort, binding), (0, lifecycle_capability_port_binding_2.bindIssueLabels)((0, issue_labels_composition_root_1.createIssueLabelRepository)(), binding), (0, push_single_action_capability_port_binding_1.bindPullRequestBranchQuery)(new pull_request_lifecycle_repository_1.PullRequestLifecycleRepository((0, github_pull_request_client_factory_1.createPullRequestLifecycleClient)()), binding), (0, push_single_action_capability_port_binding_1.bindBranchChangeSize)(new branch_compare_repository_1.BranchCompareRepository((0, github_branch_client_factory_1.createBranchComparisonClient)()), binding)), createDetectPotentialProblemsUseCase(binding), (0, check_progress_composition_root_1.createCheckProgressCompositionRoot)(binding), (0, actor_authorization_composition_root_1.createActorAuthorizationRepository)()); + return new commit_use_case_1.CommitUseCase(new notify_new_commit_on_issue_use_case_1.NotifyNewCommitOnIssueUseCase((0, push_single_action_capability_port_binding_1.bindIssueReopen)((0, issue_interaction_composition_root_1.createIssueNotificationRepository)(), binding)), new check_changes_issue_size_use_case_1.CheckChangesIssueSizeUseCase((0, lifecycle_capability_port_binding_2.bindProjectBoardCommands)(projectBoardCommandPort, binding), (0, lifecycle_capability_port_binding_2.bindIssueLabels)((0, issue_labels_composition_root_1.createIssueLabelRepository)(), binding), (0, push_single_action_capability_port_binding_1.bindPullRequestBranchQuery)(new pull_request_lifecycle_repository_1.PullRequestLifecycleRepository((0, github_pull_request_client_factory_1.createPullRequestLifecycleClient)()), binding), (0, push_single_action_capability_port_binding_1.bindBranchChangeSize)(new branch_compare_repository_1.BranchCompareRepository((0, github_branch_client_factory_1.createBranchComparisonClient)()), binding)), createDetectPotentialProblemsUseCase(binding), (0, check_progress_composition_root_1.createCheckProgressCompositionRoot)(binding), (0, actor_authorization_composition_root_1.createActorAuthorizationRepository)()); } function createMainRunRouteCompositionRoot(projectBoardCommandPort, surface) { let singleAction; @@ -83292,7 +71949,7 @@ exports.bindRepositoryTag = bindRepositoryTag; exports.bindRepositoryRelease = bindRepositoryRelease; exports.bindRepositoryDefaultBranch = bindRepositoryDefaultBranch; exports.bindIssueCommentPublication = bindIssueCommentPublication; -exports.bindIssuePushNotification = bindIssuePushNotification; +exports.bindIssueReopen = bindIssueReopen; exports.bindBranchListQuery = bindBranchListQuery; exports.bindPullRequestBranchQuery = bindPullRequestBranchQuery; exports.bindIssueProgress = bindIssueProgress; @@ -83401,10 +72058,9 @@ function bindIssueCommentPublication(port, binding) { listIssueComments: (issueNumber) => port.listIssueComments(binding.owner, binding.repository, issueNumber, binding.token), }); } -function bindIssuePushNotification(port, binding) { +function bindIssueReopen(port, binding) { return Object.freeze({ openIssue: (issueNumber) => port.openIssue(binding.owner, binding.repository, issueNumber, binding.token), - addComment: (issueNumber, comment) => port.addComment(binding.owner, binding.repository, issueNumber, comment, binding.token), }); } function bindBranchListQuery(port, binding) { @@ -85450,28 +74106,10 @@ function getUserRequestPrompt(params) { "use strict"; -/** - * Watermark appended to comments (issues and PRs) to attribute Copilot. - * Bugbot comments include commit link and note about auto-update on new commits. - */ Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.COPILOT_MARKETPLACE_URL = void 0; -exports.getCommentWatermark = getCommentWatermark; exports.stripTrailingCommentWatermarks = stripTrailingCommentWatermarks; -exports.COPILOT_MARKETPLACE_URL = 'https://github.com/marketplace/actions/copilot-github-with-super-powers'; -const DEFAULT_WATERMARK = `Made with ❤️ by [vypdev/copilot](${exports.COPILOT_MARKETPLACE_URL})`; -function commitUrl(owner, repo, sha) { - return `https://github.com/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commit/${sha}`; -} -function getCommentWatermark(options) { - if (options?.commitSha && options?.owner && options?.repo) { - const url = commitUrl(options.owner, options.repo, options.commitSha); - return `Written by [vypdev/copilot](${exports.COPILOT_MARKETPLACE_URL}) for commit [${options.commitSha}](${url}). This will update automatically on new commits.`; - } - return DEFAULT_WATERMARK; -} const TRAILING_COMMENT_WATERMARK = /\s*(?:Made with ❤️ by|Written by) \[vypdev\/copilot\]\(https:\/\/github\.com\/marketplace\/actions\/copilot-github-with-super-powers\)[^<]*<\/sup>\s*$/u; -/** Removes all trailing Copilot watermarks before a read-modify-write update. */ +/** Removes legacy trailing Copilot watermarks before a read-modify-write update. */ function stripTrailingCommentWatermarks(comment) { let stripped = comment; while (TRAILING_COMMENT_WATERMARK.test(stripped)) { @@ -85533,36 +74171,6 @@ const injectJsonAsMarkdownBlock = (title, json) => { exports.injectJsonAsMarkdownBlock = injectJsonAsMarkdownBlock; -/***/ }), - -/***/ 42277: -/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { - -"use strict"; - -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getRandomElement = void 0; -const chance_1 = __importDefault(__nccwpck_require__(78043)); -const chance = new chance_1.default(); -const getRandomElement = (list) => { - // Return undefined for empty lists - if (!list?.length) { - return undefined; - } - // Return first element for single item lists - if (list.length === 1) { - return list[0]; - } - // Use chance to get a random index - const randomIndex = chance.integer({ min: 0, max: list.length - 1 }); - return list[randomIndex]; -}; -exports.getRandomElement = getRandomElement; - - /***/ }), /***/ 91151: diff --git a/docs/bugbot/detection.mdx b/docs/bugbot/detection.mdx index cd5eb69ae..894bfe6c6 100644 --- a/docs/bugbot/detection.mdx +++ b/docs/bugbot/detection.mdx @@ -98,7 +98,13 @@ When the configured agent later reports a finding as **resolved** (e.g. after yo - **Marks the PR review thread as resolved** after the marker update succeeds. - **Re-reads GitHub** and refreshes the originating review's status block. -One stable **Bugbot status** comment in the PR conversation is the easiest aggregate view: it shows the verified head, counts by state, links to findings, the required next action, and bounded technical recovery details. The durable per-finding authority is the trusted hidden marker together with the native thread facts; the review snapshot is history, not the sole source of current truth. +One stable **Bugbot status** comment in the PR conversation is the aggregate +view. It leads with the outcome, lists only findings that need attention or +verification, and shows recovery details only when the review is incomplete. +Full-coverage clean runs have no empty count table, no “action required” block, +and no technical disclosure. The durable per-finding authority is the trusted +hidden marker together with native thread facts; the review snapshot is history, +not the sole source of current truth. --- @@ -125,7 +131,7 @@ When there are more findings than the limit: - The action publishes the first N findings as individual issue comments for issue-only work, or as inline comments in one PR review. - Overflow is added to the same PR review summary. When no PR exists, one extra issue comment summarizes it. -- On a PR, the canonical **Bugbot status** counts every accepted current finding, including overflow. Overflow rows have no thread link because they were deliberately not published individually; increase the limit or run `/copilot recheck` after addressing them. A historical review's mutable block reports its individually tracked findings and explicitly flags historical overflow instead of claiming that untracked entries are resolved. +- On a PR, the canonical **Bugbot status** accounts for every accepted current finding, including overflow. Overflow rows have no thread link because they were deliberately not published individually. Increase the configured limit or reduce the PR scope; rerun only after changing the relevant scope, limit, access, or code. A historical review's mutable block reports its individually tracked findings and explicitly flags historical overflow instead of claiming that untracked entries are resolved. So a PR analysis creates one review batch instead of N unrelated reviews and N duplicated conversation comments. diff --git a/docs/bugbot/examples.mdx b/docs/bugbot/examples.mdx index d172befaf..c664e0ad0 100644 --- a/docs/bugbot/examples.mdx +++ b/docs/bugbot/examples.mdx @@ -260,20 +260,17 @@ review with work remaining looks conceptually like this: > **Current status:** 2 findings require attention on `e6089b9`. > -> **Action required:** Review the linked threads or comment `/copilot fix all`. +> **Action:** Review the linked threads or comment `/copilot fix all`. -| State | Count | -| --- | ---: | -| Open / reopened | 1 | -| Verification required | 1 | -| Fixed | 2 | -| Obsolete | 1 | -| Dismissed | 1 | -| Unknown | 0 | +- [ ] open — [SQL query interpolates untrusted input](#discussion_r100) +- [ ] verification-required — [Retry behavior changed](#discussion_r101) `fixed`, `obsolete`, and `dismissed` are clean. `open`, `reopened`, and `verification-required` need attention. `unknown` and a partial/failed -projection replace the action text with `/copilot recheck` and fail closed. +projection fail closed and expose bounded recovery details. A provider or state +error can recommend one `/copilot recheck` after its cause is corrected. Fixed +coverage limits instead tell the reader to change scope, limits, or access; +repeating the same command against unchanged capped evidence is not useful. A superseded run does not edit this card; the newer head owns the next update. --- diff --git a/docs/bugbot/failure-scenarios.mdx b/docs/bugbot/failure-scenarios.mdx index cf8dbc583..5523c6361 100644 --- a/docs/bugbot/failure-scenarios.mdx +++ b/docs/bugbot/failure-scenarios.mdx @@ -25,7 +25,7 @@ description: Diagnose terminal failures across detection, publication, autofix, Use the canonical **Bugbot status** card and `Copilot / Review` Check for the aggregate state. Run `/copilot recheck` to repair the generated status block; the historical snapshot text is intentionally retained. - Open **Coverage** and **Technical details**. A fixed context cap is `partial`: retained evidence may produce findings, but Bugbot cannot declare the whole PR clean or resolve omitted history. Inspect the named source, split an oversized PR if appropriate, then run `/copilot recheck`. A provider or permission error is `unavailable` and aborts before analysis; correct access or wait for the rate limit before retrying. Every final-read surface is tracked independently, so an empty verified result is not confused with a failed read. A previously observed non-clean finding that disappears remains visible as `unknown`; only fully resolved findings may be omitted safely. + Open **Incomplete coverage**. A fixed context cap is `partial`: retained evidence may produce findings, but Bugbot cannot declare the whole PR clean or resolve omitted history. Inspect the named source, split an oversized PR, increase the applicable limit, or correct access. Rerun only after one of those inputs changes; repeating `/copilot recheck` against the same capped evidence cannot improve coverage. A provider or permission error is `unavailable` and aborts before analysis; correct access or wait for the rate limit before retrying. Every final-read surface is tracked independently, so an empty verified result is not confused with a failed read. A previously observed non-clean finding that disappears remains visible as `unknown`; only fully resolved findings may be omitted safely. Bugbot rejects the result instead of treating an omitted aggregate or missing, extra, fractional, negative, non-finite, or overflowing state counts as zero. It also rejects duplicate telemetry or a valid snapshot beside a malformed owned sibling; valid zero counts cannot conceal invalid telemetry. `completed`, `no-findings`, `partial`, and `dry-run` outcomes must include all seven canonical counts. The Action and eligible Review Check fail closed, lifecycle stays blocked, and `/copilot status` does not invent counts. Metadata-only, `skipped`, `superseded`, and `failed` results may omit counts because none can claim a clean analysis. Inspect the producing workflow result, correct the result schema or retry the current released Action, then run `/copilot recheck`. diff --git a/docs/configuration.mdx b/docs/configuration.mdx index 6f1bf8ea9..1da0b29fc 100644 --- a/docs/configuration.mdx +++ b/docs/configuration.mdx @@ -146,7 +146,7 @@ Copilot provides extensive configuration options to customize your workflow. Use overrides a workflow proven to lack `merge_group: checks_requested`. - + ## Single Actions - `single-action`: Single action to run (e.g. `check_progress_action` or `think_action`). See [Single Actions](/single-actions). @@ -159,15 +159,20 @@ Copilot provides extensive configuration options to customize your workflow. Use - `single-action-comment-mode`: `create`, `replace`, or `append`; defaults to `create` without an ID and `replace` with an ID - `inactivity-threshold-hours`: Inactivity window for `close_inactive_issues_action` (default: `168` hours; valid range: `1`–`8760`) - ## Image Configuration + ## Deprecated image compatibility - **Display:** `images-on-issue`, `images-on-pull-request`, `images-on-commit` (default: "true") + `images-on-issue`, `images-on-pull-request`, and `images-on-commit` now + default to `false`. Semantic plans, progress cards, Bugbot status, branch + synchronization, lifecycle explanations, and push handling never add a + decorative image. The booleans and URL pools remain accepted for one + compatibility window so existing workflows do not fail schema validation; + new configurations should omit them. - **Issue images:** `images-issue-automatic`, `images-issue-feature`, `images-issue-bugfix`, `images-issue-docs`, `images-issue-chore`, `images-issue-hotfix`, `images-issue-release` + **Deprecated issue pools:** `images-issue-automatic`, `images-issue-feature`, `images-issue-bugfix`, `images-issue-docs`, `images-issue-chore`, `images-issue-hotfix`, `images-issue-release` - **Pull request images:** `images-pull-request-automatic`, `images-pull-request-feature`, `images-pull-request-bugfix`, `images-pull-request-docs`, `images-pull-request-chore`, `images-pull-request-hotfix`, `images-pull-request-release` + **Deprecated pull-request pools:** `images-pull-request-automatic`, `images-pull-request-feature`, `images-pull-request-bugfix`, `images-pull-request-docs`, `images-pull-request-chore`, `images-pull-request-hotfix`, `images-pull-request-release` - **Commit images:** `images-commit-automatic`, `images-commit-feature`, `images-commit-bugfix`, `images-commit-docs`, `images-commit-chore`, `images-commit-hotfix`, `images-commit-release` + **Deprecated commit pools:** `images-commit-automatic`, `images-commit-feature`, `images-commit-bugfix`, `images-commit-docs`, `images-commit-chore`, `images-commit-hotfix`, `images-commit-release` diff --git a/docs/development/architecture.mdx b/docs/development/architecture.mdx index ee8d57bb8..38ad547d6 100644 --- a/docs/development/architecture.mdx +++ b/docs/development/architecture.mdx @@ -6,6 +6,15 @@ description: Clean Architecture boundaries for agent execution and workflow auto The repository separates semantic application ports from provider-specific adapters. +GitHub conversation output crosses a closed publication boundary. Capabilities +produce typed `none`, `reply`, `status`, `transition`, or `inline-finding` +intents; the compatibility layer maps only reviewed semantic payloads and can +never render `Result.steps`, reminders, errors, images, or debug logs. Status +and reply workflows validate exact bot ownership, stable correlation or topic +identity, and semantic digests before creating or updating a comment. The +checked-in publication mutation inventory fails CI if a new application use +case writes a comment or review without an explicit architectural decision. + - Application: use cases, pure policies (agent configuration, reviewer/assignee selection, and result publication), and semantic ports. - Data/repository: external adapters for agent execution, authentication, provisioning, GitHub, and Git. diff --git a/docs/features.mdx b/docs/features.mdx index a4b9ce19b..ff93d6817 100644 --- a/docs/features.mdx +++ b/docs/features.mdx @@ -32,14 +32,13 @@ When the workflow runs on `issues` (opened, edited, labeled, unlabeled, etc.): | Feature | Description | |--------|-------------| | **Branch creation** | Creates feature, bugfix, docs, chore, and release branches from the configured development line. A release records the exact development SHA at the cut; a hotfix instead starts from the commit behind the selected production version tag. Optional: non-release/hotfix types require a launcher label (for example `branched`) unless `branch-management-always` is true. | -| **Branch–issue linking** | Links the new branch to the issue and adds a comment summarizing the actions taken. | +| **Branch–issue linking** | Links the new branch to the issue. The branch and native issue metadata are the visible evidence; routine linkage does not add a recap comment. | | **Project linking** | Adds the issue to the configured GitHub Projects (by `project-ids`) and moves it to the configured column (e.g. "Todo", "In Progress"). | | **Assignees** | Assigns up to `desired-assignees-count` members to the issue (creator preferred if in org). | | **Issue type** | Sets the GitHub issue type (Task, Bug, Feature, Documentation, etc.) from labels. | | **Emoji titles** | Optionally adds emojis to issue titles based on labels (`emoji-labeled-title`). | | **Size labels** | Assigns size labels (XS–XXL) and checks size thresholds (lines, files, commits) for prioritization. | -| **Comments & images** | Posts comments with optional images (per branch type: feature, bugfix, docs, chore, hotfix, release). | -| **Smart workflow guidance** | Comments can include Git-Flow reminders and next steps. | +| **Planning guidance** | When planning is requested, maintains one bounded plan card with the outcome and next action. Internal execution steps and Git-Flow reminders stay in the Job Summary. | | **Lifecycle labels** | Maintains an exclusive durable `state:*` phase, an optional `state:ai-processing` activity marker, and an optional human-waiting label. Activity can coexist with the durable phase and is removed when the agent run finishes. | ### 2. Pull request events (`on: pull_request`) @@ -48,12 +47,12 @@ When the workflow runs on `pull_request` (opened, edited, etc.): | Feature | Description | |--------|-------------| -| **PR–issue linking** | Links the pull request to the issue associated with its branch and posts a comment. | +| **PR–issue linking** | Links the pull request to the issue associated with its branch and relies on GitHub's native linked-PR UI. | | **Project linking** | Adds the PR to the configured GitHub Projects and moves it to the configured column. | | **Reviewers** | Assigns up to `desired-reviewers-count` reviewers. | | **Priority & size** | Applies priority and size checks (labels and thresholds). | | **AI PR description** | When `ai-pull-request-description-mode` is `replace`, `append`, or `preserve`, the selected agent CLI can generate a PR description from the issue and branch diff. `disabled` turns the feature off. See [Pull Requests → AI-generated PR description](/pull-requests/ai-description). | -| **Comments & images** | Posts comments with optional images per branch type. | +| **Conversation UX** | Publishes only purpose-specific replies and durable status cards. It does not add a generic action recap or decorative image. | | **Bugbot review** | Reviews the full PR on open/reopen and the new commit range on synchronize; publishes historical review snapshots and stable line findings, then reconciles native threads, review status blocks, one current PR card, and the Check from a provider-verified projection. | ### 3. Push events (`on: push`) @@ -62,12 +61,11 @@ When the workflow runs on `push` (e.g. to any branch): | Feature | Description | |--------|-------------| -| **Commit notifications** | For branches linked to an issue, posts a comment on the issue with commit messages and links. | +| **Push handling** | Treats the push as an internal signal. It does not post a per-commit summary; when configured, it can reopen the linked issue through GitHub's native state transition. | | **Commit prefix check** | Warns if commit messages do not follow the prefix derived from the branch name (using `commit-prefix-transforms`). | | **Reopen issue** | If `reopen-issue-on-push` is true, reopens the issue when new commits are pushed to its branch. | | **Size & progress** | Computes size (XS–XXL) and progress (0–100%) from the branch diff; updates the **issue** and any **open PRs** for that branch with the same labels. Uses the configured agent runtime. No separate workflow is needed. | | **Bugbot (potential problems)** | The configured agent analyzes the branch vs base and reports findings as **issue comments** when there is no PR, or as one summarized review with inline findings when a PR is available; it updates stored findings and **marks PR review threads as resolved** when applicable. Configurable via `bugbot-severity` and `ai-ignore-files`. See [Bugbot](/bugbot), [issue findings](/bugbot/detection#on-the-issue-when-no-pr-is-available), and [pull-request findings](/bugbot/detection#on-the-pull-request). | -| **Comments & images** | Posts commit summary comments with optional images. | | **Finding reconciliation** | Carries stable finding fingerprints across pushes and projects open, reopened, fixed, obsolete, dismissed, verification-required, or unknown without creating duplicate status comments. | | **Branch drift observer** | A separate lightweight workflow watches pushes on every branch, resolves open issue/PR branch relationships, and maintains one synchronization notice when a child is behind. It does not load Bugbot or an agent. See [Branch synchronization](/issues/branch-synchronization). | @@ -92,7 +90,7 @@ When you set `single-action` (and the inputs required by that action), Copilot r |--------------------|----------------|-------------| | **`check_progress_action`** | `single-action-issue` | Runs progress check on demand (e.g. without pushing). Progress is normally updated automatically on every push; use this to re-run the check or when no push workflow is configured. | | **`detect_potential_problems_action`** | `single-action-issue` | Bugbot: the configured agent analyzes the branch vs base; reports findings on the issue when no PR exists, or in one summarized PR review when one is available; updates stored findings and resolves PR threads when findings are fixed. | -| **`recommend_steps_action`** | `single-action-issue` | Uses the configured agent to recommend implementation steps from the issue description; posts a comment on the issue. | +| **`recommend_steps_action`** | `single-action-issue` | Uses the configured agent to recommend implementation steps from the issue description; creates or updates one plan card on the issue. | | **`think_action`** | — | Uses the configured agent for deep code analysis and change proposals (reasoning over the codebase). No issue required. | | **`initial_setup`** | — | Performs initial setup: creates labels, issue types, verifies access. If the repo has no version tags, creates default tag `v1.0.0` so release/hotfix issues get a base version. No issue required. | | **`create_release`** | Internal: issue, operation ID, version | Creates or verifies the GitHub Release and its exact durable operation/SHA receipt. Supplied workflows only. | @@ -128,8 +126,8 @@ Codex is the default runtime for the repository's AI feature paths. OpenCode and | Feature | Where it runs | Description | |--------|----------------|-------------| -| **Check progress** | Push (commit) pipeline; optional single action `check_progress_action` / CLI `check-progress` | On every push, the configured agent compares issue vs branch diff and updates the progress label on the issue and on any open PRs for that branch. | -| **Bugbot (potential problems)** | Push (commit) pipeline; optional single action `detect_potential_problems_action` / CLI `detect-potential-problems` | Analyzes branch vs base and posts findings as **issue comments** when no PR exists, or as one summarized review with inline comments when a PR is available; updates stored findings and resolves PR threads when findings are fixed. Configurable: `bugbot-severity`, `ai-ignore-files`. | +| **Check progress** | Push (commit) pipeline; optional single action `check_progress_action` / CLI `check-progress` | Compares issue vs branch diff, updates progress labels, and reconciles one issue progress card only when its semantic projection changes. It does not post per-commit narration. | +| **Bugbot (potential problems)** | Push (commit) pipeline; optional single action `detect_potential_problems_action` / CLI `detect-potential-problems` | Analyzes branch vs base and posts bounded findings near code plus one concise aggregate status card. A clean full-coverage card omits empty tables and technical detail; incomplete coverage names an effective recovery action instead of requesting identical rechecks. | | **Do user request** | Issue comment; PR review comment | When you comment asking to perform a change in the repo (or use `/copilot implement `), the configured agent applies the changes in the workspace, runs verify commands, and the action commits and pushes with a generic message. Organization repositories require an org member; personal repositories require the owner or a `push`/`maintain`/`admin` collaborator. Uses the same `bugbot-fix-verify-commands` and agent CLI setup. | | **Think / reasoning** | Issue/PR comment pipeline; single action `think_action` | Deep code analysis and change proposals (configured agent CLI). On comments, answers an explicit `/copilot` command or exact bot mention. Runs when the addressed comment was not a fix/do request or when the user is not allowed to trigger file-modifying actions. | | **Explicit Copilot commands** | Issue and PR comments | `/copilot help`, `/copilot plan`, `/copilot clarify`, `/copilot estimate`, `/copilot test-plan`, `/copilot explain`, `/copilot diagnose`, `/copilot analyze`, `/copilot status`, `/copilot review`, `/copilot findings`, `/copilot recheck`, `/copilot fix`, `/copilot dismiss`, `/copilot remember`, `/copilot implement`, and `/copilot sync-branch` provide a bounded, predictable interface. | @@ -139,6 +137,21 @@ Codex is the default runtime for the repository's AI feature paths. OpenCode and | **Copilot** | CLI `copilot do` | Code analysis and file edits via the configured agent runtime. | | **Recommend steps** | Single action / CLI | Suggests implementation steps from the issue description (configured agent CLI). | +## GitHub conversation UX + +Copilot treats issue comments, PR comments, reviews, and durable dashboards as +product UI. New normal issues receive one implementation-plan card; later edits +update that card. Pushes may update one progress card. Branch drift, Bugbot, and +release/hotfix orchestration each own one stateful card. Explicit commands and +mentions receive at most one correlated reply. + +Routine labels, assignees, project moves, title normalization, PR linkage, +description refreshes, pushes, reopens, merges, and closes do not create a +generic roll-up. Detailed steps and errors remain in the Job Summary and logs. +Hidden markers provide stable identity and are trusted only when the comment is +authored by the configured bot. Decorative image inputs default to `false` and +are deprecated. + --- diff --git a/docs/issues/branch-synchronization.mdx b/docs/issues/branch-synchronization.mdx index 71e2cc70f..a5c5ab042 100644 --- a/docs/issues/branch-synchronization.mdx +++ b/docs/issues/branch-synchronization.mdx @@ -103,6 +103,11 @@ Successful reports include the parent and working branches, verification count, ## Why the normal commit workflow remains limited -`copilot_commit.yml` is an issue-centric, agent-capable pipeline: it can publish commit summaries, calculate size/progress, and run Bugbot. Its default exclusion of `master` and `develop` avoids irrelevant issue lookup, agent provisioning, duplicate PR analysis, higher API/model usage, and bot feedback loops on integration branches. +`copilot_commit.yml` is an issue-centric, agent-capable pipeline: it can +calculate size/progress and run Bugbot. Pushes themselves stay silent; a +purpose-specific progress or review surface is updated only when that outcome +exists. Its default exclusion of `master` and `develop` avoids irrelevant issue +lookup, agent provisioning, duplicate PR analysis, higher API/model usage, and +bot feedback loops on integration branches. The lightweight observer provides the useful part of all-branch visibility without broadening that expensive workflow. Repositories may still remove the commit-workflow exclusions deliberately, but branch synchronization does not require doing so. diff --git a/docs/issues/comment-commands.mdx b/docs/issues/comment-commands.mdx index 792de130b..79aadda26 100644 --- a/docs/issues/comment-commands.mdx +++ b/docs/issues/comment-commands.mdx @@ -54,6 +54,11 @@ resolved. If the stored workflow result owns malformed finding-state evidence, the command says that evidence is invalid and directs you to the workflow result; it never substitutes zero or a clean state. +`/copilot help` and `/copilot status` use correlated reply markers. A webhook +retry with the same source comment does not create another response. These +explicit replies contain only their requested content; they are never wrapped +in an “Automatic Actions” summary. + GitHub delivers a comment in the main PR conversation as an `issue_comment` event. Copilot uses GitHub's PR marker and exact PR number to keep that transport detail from changing the target: read-only commands such as `/copilot recheck` @@ -96,7 +101,10 @@ conversation or machine-authored status comments. ## New-issue welcome -When a new issue is opened, Copilot adds one contextual welcome message. For question/help issues it is combined with the initial answer; for other issue types it is combined with the recommendation when available. If no recommendation can run, the static welcome still explains how to use `/copilot help` and `@vypbot`. +When a new normal issue has a generated plan, that single plan card includes a +one-line command hint and no separate welcome. A question/help issue receives +its direct answer without a second onboarding comment. If neither plan nor +answer can run, one static welcome explains `/copilot help` and `@vypbot`. The welcome contains a hidden marker so later workflow changes can recognize it without relying on the visible wording. diff --git a/docs/issues/configuration.mdx b/docs/issues/configuration.mdx index 52c7e39d0..3a06340ce 100644 --- a/docs/issues/configuration.mdx +++ b/docs/issues/configuration.mdx @@ -93,37 +93,44 @@ The following parameters can be configured in the workflow: - `desired-assignees-count`: Number of assignees desired for the issue (default: "1", max: 10) - `desired-reviewers-count`: Number of reviewers desired for pull requests (default: "1", max: 15) -#### Image Configuration -- `images-on-issue`: Include images in issue comments (default: "true") -- `images-on-pull-request`: Include images in pull request comments (default: "true") -- `images-on-commit`: Include images in commit comments (default: "true") - -##### Issue Images -- `images-issue-automatic`: Image URLs to display in automatic action issue comments -- `images-issue-feature`: Image URLs to display in feature action issue comments -- `images-issue-bugfix`: Image URLs to display in bugfix action issue comments -- `images-issue-docs`: Image URLs to display in docs action issue comments -- `images-issue-chore`: Image URLs to display in chore action issue comments -- `images-issue-hotfix`: Image URLs to display in hotfix action issue comments -- `images-issue-release`: Image URLs to display in release action issue comments - -##### Pull Request Images -- `images-pull-request-automatic`: Image URLs to display in automatic action pull request comments -- `images-pull-request-feature`: Image URLs to display in feature action pull request comments -- `images-pull-request-bugfix`: Image URLs to display in bugfix action pull request comments -- `images-pull-request-docs`: Image URLs to display in docs action pull request comments -- `images-pull-request-chore`: Image URLs to display in chore action pull request comments -- `images-pull-request-hotfix`: Image URLs to display in hotfix action pull request comments -- `images-pull-request-release`: Image URLs to display in release action pull request comments - -##### Commit Images -- `images-commit-automatic`: Image URLs to display in automatic action commit comments -- `images-commit-feature`: Image URLs to display in feature action commit comments -- `images-commit-bugfix`: Image URLs to display in bugfix action commit comments -- `images-commit-docs`: Image URLs to display in docs action commit comments -- `images-commit-chore`: Image URLs to display in chore action commit comments -- `images-commit-hotfix`: Image URLs to display in hotfix action commit comments -- `images-commit-release`: Image URLs to display in release action commit comments +#### Deprecated Image Compatibility +- `images-on-issue`: Deprecated compatibility toggle (default: `false`) +- `images-on-pull-request`: Deprecated compatibility toggle (default: `false`) +- `images-on-commit`: Deprecated compatibility toggle (default: `false`) + +Semantic comments and cards do not render decorative images. The following URL +pools remain accepted during the compatibility window, but new workflows should +omit them. + +##### Deprecated issue image pools +- `images-issue-automatic` +- `images-issue-feature` +- `images-issue-bugfix` +- `images-issue-docs` +- `images-issue-chore` +- `images-issue-hotfix` +- `images-issue-release` + +##### Deprecated pull-request image pools +- `images-pull-request-automatic` +- `images-pull-request-feature` +- `images-pull-request-bugfix` +- `images-pull-request-docs` +- `images-pull-request-chore` +- `images-pull-request-hotfix` +- `images-pull-request-release` + +##### Deprecated commit image pools +- `images-commit-automatic` +- `images-commit-feature` +- `images-commit-bugfix` +- `images-commit-docs` +- `images-commit-chore` +- `images-commit-hotfix` +- `images-commit-release` + +All URL-pool inputs above are ignored by semantic publication. They remain in +the action schema only so an existing workflow does not fail while migrating. #### AI agent selection The workflow can select one provider for both findings and fixer operations. The selected transport is resolved before execution; unsupported combinations fail instead of silently falling back to OpenCode. diff --git a/docs/issues/notifications-and-auto-close.mdx b/docs/issues/notifications-and-auto-close.mdx index 3633c6436..d4ce014e8 100644 --- a/docs/issues/notifications-and-auto-close.mdx +++ b/docs/issues/notifications-and-auto-close.mdx @@ -1,20 +1,33 @@ --- -title: Notifications and auto-close -description: Commit notifications on the issue, reopen on push, and auto-close when branches are merged. +title: Status messages and auto-close +description: Quiet push handling, durable status cards, reopen behavior, and terminal issue explanations. --- -# Notifications and auto-close +# Status messages and auto-close -Copilot keeps the issue in sync with the branch: it **notifies** you of new commits and can **reopen** a closed issue when pushes happen. When the branch is **merged**, it can **automatically close** the issue. This page describes these behaviors and the inputs that control them. +Copilot keeps issue state synchronized without narrating every internal step. +Routine metadata changes, each pushed commit, reopen success, merge success, and +close success rely on GitHub's native UI and the workflow Job Summary. A +conversation message appears only when it provides an implementation plan, +current progress, an action, a direct answer, or a terminal policy explanation. -## Commit notifications (real-time code tracking) +## Push and progress behavior -When the **push (commit)** workflow runs (on push to a branch linked to an issue), the action can **post a comment on the issue** with the new commit messages and links. That way, everyone following the issue sees progress in real time. +When the push workflow runs for a branch linked to an issue, Copilot updates one +owned progress card only when the progress projection changes. It does not post +a commit list, image, reopen notice, debug report, or generic “Actions” summary. +The workflow Job Summary remains the place for bounded operational detail. -- **What is posted:** Commit summary (messages, authors, links to commits). Optionally **images** per branch type (feature, bugfix, etc.) if **`images-on-commit`** and the corresponding `images-commit-*` inputs are set. See [Configuration](/issues/configuration). -- **Where:** The comment is posted on the **issue** associated with the branch (the issue number is derived from the branch name, e.g. `feature/123-title` → issue `123`). +- **Where:** the issue associated with the branch (for example, + `feature/123-title` → issue `123`). +- **Identity:** one hidden, bot-owned semantic marker identifies the progress + card. Equivalent retries create or update nothing. +- **Content:** outcome, current status, and the next useful action only. Agent + reasoning and internal step names are excluded. -Commit notifications are part of the **Commit** workflow, not the Issue workflow. Ensure you have a workflow that runs on **push** (e.g. `copilot_commit.yml`) and that the Copilot step has `token` and any configured agent CLI inputs. See [How to use](/how-to-use) and [Bugbot](/bugbot) for push-related features. +Push processing is part of the **Commit** workflow, not the Issue workflow. +Ensure `copilot_commit.yml` is installed and has its token and configured agent +inputs. See [How to use](/how-to-use) and [Bugbot](/bugbot). ## Reopen issue on push @@ -24,7 +37,8 @@ If an issue was **closed** but someone pushes again to its branch, you may want |-------|---------|-------------| | **`reopen-issue-on-push`** | `true` | When the push workflow runs and the branch is linked to an issue that is **closed**, the action **reopens** that issue. Set to `false` to leave closed issues closed. | -This applies only when the **Commit** workflow runs (on push); the Issue workflow does not reopen issues by itself. +Reopen is a native state transition. A successful reopen creates no separate +conversation comment. This applies only when the **Commit** workflow runs. ## Auto-close when branch is merged @@ -32,6 +46,7 @@ When the **branch** created for the issue (e.g. `feature/123-title`) is **merged - **How it works:** The action listens for the merge (via the push/PR pipeline and branch state). When the branch no longer exists (merged and deleted) or the merge is detected, it closes the linked issue. - **No extra input** is required for this behavior; it is part of the normal flow when the Commit and/or PR workflows run and the branch is merged. +- **No success comment** is added. GitHub's closed state is the authoritative UI. ## Auto-close by inactivity @@ -44,13 +59,13 @@ repositories do not change behavior until `copilot setup` enables the - **Eligibility:** open issues carrying `state:awaiting-maintainer` or `state:awaiting-issue-author`. - **Activity:** GitHub's `updated_at` timestamp is used as the last observed issue activity. Pull requests, issues with `state:ai-processing`, missing/future timestamps, and recently updated issues are skipped. - **Default threshold:** 168 hours (7 days), configurable with `inactivity-threshold-hours` or the `INACTIVITY_THRESHOLD_HOURS` Repository Variable. Valid values are 1–8760 hours. -- **Safety:** the issue is fetched again immediately before closing. If its state, waiting label, activity timestamp, or processing marker changed, it is left open. A closing comment explains the action; adding a new comment allows the issue to be reopened and re-evaluated. +- **Safety:** the issue is fetched again immediately before closing. If its state, waiting label, activity timestamp, or processing marker changed, it is left open. One terminal explanation records the threshold and reopening path; it uses a stable semantic marker and the configured issue locale. ## Summary | Behavior | Controlled by | Where it runs | |----------|---------------|---------------| -| Commit notifications on issue | Commit workflow + optional images config | Push (Commit) workflow | +| Progress card update | Changed progress projection | Push (Commit) workflow | | Reopen closed issue on push | `reopen-issue-on-push` (default: true) | Push (Commit) workflow | | Auto-close issue when branch merged | Built-in | Push / PR workflow when merge is detected | | Auto-close waiting issue after inactivity | `inactiveIssueClosure` + `inactivity-threshold-hours` | Scheduled workflow every 6 hours | @@ -58,5 +73,5 @@ repositories do not change behavior until `copilot setup` enables the ## Next steps - **[Workflow setup](/issues/workflow-setup)** — Issue workflow events and scheduled inactivity cleanup. -- **[Configuration](/issues/configuration)** — `reopen-issue-on-push`, images on commit. +- **[Configuration](/issues/configuration)** — `reopen-issue-on-push`, locale, and deprecated image compatibility. - [How to use](/how-to-use) — Full setup including Commit workflow. diff --git a/docs/issues/type/bugfix.mdx b/docs/issues/type/bugfix.mdx index 7d10404c9..d16fca4ec 100644 --- a/docs/issues/type/bugfix.mdx +++ b/docs/issues/type/bugfix.mdx @@ -61,24 +61,12 @@ jobs: development-branch: develop # [!code highlight] ``` -## Images +## Conversation presentation -You can configure custom images to be displayed in the comments of bugfix issues. These images will be randomly selected and displayed when Copilot performs actions on the issue. - -To configure the images, provide a comma-separated list of image URLs in the `images-issue-bugfix` input: - -```yml -jobs: - git-board-issues: - name: Git Board - Issue - runs-on: ubuntu-latest - steps: - - uses: vypdev/copilot@v3 - with: - images-issue-bugfix: url1, url2, url3 # [!code highlight] -``` - - +Bugfix issues receive semantic plan and progress cards only when those states +are useful. Routine automation does not create an “Actions” summary or a +decorative image. The legacy `images-issue-bugfix` input remains accepted only +for configuration compatibility. ## Template diff --git a/docs/issues/type/chore.mdx b/docs/issues/type/chore.mdx index f2505e156..6c2a3c23f 100644 --- a/docs/issues/type/chore.mdx +++ b/docs/issues/type/chore.mdx @@ -61,24 +61,12 @@ jobs: development-branch: develop # [!code highlight] ``` -## Images +## Conversation presentation -You can configure custom images to be displayed in the comments of chore issues. These images will be randomly selected and displayed when Copilot performs actions on the issue. - -To configure the images, provide a comma-separated list of image URLs in the `images-issue-chore` input: - -```yml -jobs: - git-board-issues: - name: Git Board - Issue - runs-on: ubuntu-latest - steps: - - uses: vypdev/copilot@v3 - with: - images-issue-chore: url1, url2, url3 # [!code highlight] -``` - - +Chore issues receive semantic plan and progress cards only when those states +are useful. Routine automation does not create an “Actions” summary or a +decorative image. The legacy `images-issue-chore` input remains accepted only +for configuration compatibility. ## Template diff --git a/docs/issues/type/docs.mdx b/docs/issues/type/docs.mdx index ffd9f82cf..3c1af80b7 100644 --- a/docs/issues/type/docs.mdx +++ b/docs/issues/type/docs.mdx @@ -61,24 +61,12 @@ jobs: development-branch: develop # [!code highlight] ``` -## Images +## Conversation presentation -You can configure custom images to be displayed in the comments of documentation issues. These images will be randomly selected and displayed when Copilot performs actions on the issue. - -To configure the images, provide a comma-separated list of image URLs in the `images-issue-docs` input: - -```yml -jobs: - git-board-issues: - name: Git Board - Issue - runs-on: ubuntu-latest - steps: - - uses: vypdev/copilot@v3 - with: - images-issue-docs: url1, url2, url3 # [!code highlight] -``` - - +Documentation issues receive semantic plan and progress cards only when those +states are useful. Routine automation does not create an “Actions” summary or a +decorative image. The legacy `images-issue-docs` input remains accepted only +for configuration compatibility. ## Template diff --git a/docs/issues/type/feature.mdx b/docs/issues/type/feature.mdx index b5f190345..cb92afd1e 100644 --- a/docs/issues/type/feature.mdx +++ b/docs/issues/type/feature.mdx @@ -61,24 +61,12 @@ jobs: development-branch: develop # [!code highlight] ``` -## Images +## Conversation presentation -You can configure custom images to be displayed in the comments of feature issues. These images will be randomly selected and displayed when Copilot performs actions on the issue. - -To configure the images, provide a comma-separated list of image URLs in the `images-issue-feature` input: - -```yml -jobs: - git-board-issues: - name: Git Board - Issue - runs-on: ubuntu-latest - steps: - - uses: vypdev/copilot@v3 - with: - images-issue-feature: url1, url2, url3 # [!code highlight] -``` - - +Feature issues receive semantic plan and progress cards only when those states +are useful. Routine automation does not create an “Actions” summary or a +decorative image. The legacy `images-issue-feature` input remains accepted only +for configuration compatibility. ## Template diff --git a/docs/issues/type/hotfix.mdx b/docs/issues/type/hotfix.mdx index 105bb52bf..cd2f5aaa5 100644 --- a/docs/issues/type/hotfix.mdx +++ b/docs/issues/type/hotfix.mdx @@ -141,24 +141,12 @@ jobs: main-branch: master # [!code highlight] ``` -## Images +## Conversation presentation -You can configure custom images to be displayed in the comments of hotfix issues. These images will be randomly selected and displayed when Copilot performs actions on the issue. - -To configure the images, provide a comma-separated list of image URLs in the `images-issue-hotfix` input: - -```yml -jobs: - git-board-issues: - name: Git Board - Issue - runs-on: ubuntu-latest - steps: - - uses: vypdev/copilot@v3 - with: - images-issue-hotfix: url1, url2, url3 # [!code highlight] -``` - - +The durable hotfix dashboard owns the issue presentation. Routine orchestration +does not create an “Actions” summary or a decorative image. The legacy +`images-issue-hotfix` input remains accepted only for configuration +compatibility. ## Template You can use this template for hotfix issues that helps capture all the necessary information. The template includes fields for: diff --git a/docs/issues/type/release.mdx b/docs/issues/type/release.mdx index 0f41ae18d..84eacf0ff 100644 --- a/docs/issues/type/release.mdx +++ b/docs/issues/type/release.mdx @@ -137,24 +137,12 @@ jobs: development-branch: development # [!code highlight] ``` -## Images +## Conversation presentation -You can configure custom images to be displayed in the comments of release issues. These images will be randomly selected and displayed when Copilot performs actions on the issue. - -To configure the images, provide a comma-separated list of image URLs in the `images-issue-release` input: - -```yml -jobs: - git-board-issues: - name: Git Board - Issue - runs-on: ubuntu-latest - steps: - - uses: vypdev/copilot@v3 - with: - images-issue-release: url1, url2, url3 # [!code highlight] -``` - - +The durable release dashboard owns the issue presentation. Routine +orchestration does not create an “Actions” summary or a decorative image. The +legacy `images-issue-release` input remains accepted only for configuration +compatibility. ## Template You can use this template for release issues that helps capture all the necessary information. The template includes fields for: diff --git a/docs/pull-requests/capabilities.mdx b/docs/pull-requests/capabilities.mdx index 843d3bc21..2cbafbe84 100644 --- a/docs/pull-requests/capabilities.mdx +++ b/docs/pull-requests/capabilities.mdx @@ -1,18 +1,18 @@ --- title: Capabilities -description: What the action does on pull requests: linking, projects, reviewers, size, AI description, comments. +description: What the action does on pull requests: linking, projects, reviewers, size, AI description, and semantic status. --- # What the action does on pull requests -This page describes each **capability** the action performs when it runs on a pull request: PR–issue linking, project linking, reviewers, size and priority labels, AI-generated description, and comments with images. +This page describes each **capability** the action performs when it runs on a pull request: PR–issue linking, project linking, reviewers, size and priority labels, AI-generated description, and semantic status. ## PR–issue linking The action **links the pull request to the issue** associated with its branch. The issue number is inferred from the **branch name** (e.g. `feature/123-add-login` → issue `123`). It then: - Creates the **link** in GitHub (so the issue shows “Linked pull requests” and the PR shows the issue). -- Can **post a comment** on the PR with the link or a short summary (depending on configuration). +- Relies on GitHub's native linked-PR UI; routine linkage does not create a summary comment. GitHub recognizes a closing reference for linkage when the PR targets the repository's default branch. Copilot therefore reads the current body of the @@ -73,14 +73,15 @@ Thresholds are defined in [Configuration](/configuration) (e.g. `size-m-threshol See [AI PR description](/pull-requests/ai-description) for details, optional issue context, and how to enable it in your workflow. -## Comments and images +## Conversation messages -The action can **post a comment** on the PR when it runs (e.g. on open or sync). The comment can include: +Routine PR metadata, linking, reviewer assignment, size labels, description +refreshes, and merge/close success do not create generic comments. Useful state +is shown in its owning surface: the PR description, native metadata, one Bugbot +status card, bounded inline findings, Checks, and the workflow Job Summary. -- A short summary or link to the linked issue. -- **Images** per branch type (feature, bugfix, docs, chore, hotfix, release) if **`images-on-pull-request`** is `true` and the corresponding **`images-pull-request-*`** inputs are set (e.g. `images-pull-request-feature` with image URLs). - -Use this for team branding or to show different visuals per type of PR. See [Configuration](/pull-requests/configuration) and the main [Configuration](/configuration) for image inputs. +The old image toggles and URL pools default to `false` and are deprecated. +Semantic PR messages never include decorative images. ## Bugbot on PRs diff --git a/docs/pull-requests/configuration.mdx b/docs/pull-requests/configuration.mdx index 356f35268..edfbc2a35 100644 --- a/docs/pull-requests/configuration.mdx +++ b/docs/pull-requests/configuration.mdx @@ -29,15 +29,18 @@ See [AI PR description](/pull-requests/ai-description) for how the description i Copilot preserves the original review comment byte-for-byte. For an addressed request in another language it performs one bounded adaptation before domain work, keeps commands and technical operands intact, and adds escaped translation context to the bot-owned response only when translation occurred. -## Images on pull requests +## Deprecated image inputs | Input | Description | Default | |-------|-------------|---------| -| `images-on-pull-request` | Enable images in PR comments | "true" | -| `images-pull-request-automatic` | Image URLs for automatic PR comments | (built-in) | -| `images-pull-request-feature` | Image URLs for feature PR comments | (built-in) | -| `images-pull-request-bugfix` | Image URLs for bugfix PR comments | (built-in) | -| … | Same pattern for `docs`, `chore`, `hotfix`, `release` | — | +| `images-on-pull-request` | Deprecated compatibility toggle; semantic PR messages do not render decorative images | `false` | +| `images-pull-request-automatic` | Deprecated URL pool | empty | +| `images-pull-request-feature` | Deprecated URL pool | empty | +| `images-pull-request-bugfix` | Deprecated URL pool | empty | +| … | Same deprecated pattern for `docs`, `chore`, `hotfix`, `release` | empty | + +New workflows should omit these inputs. They remain accepted temporarily for +configuration compatibility. ## Other diff --git a/docs/pull-requests/examples.mdx b/docs/pull-requests/examples.mdx index 95bb02162..ccb3d8457 100644 --- a/docs/pull-requests/examples.mdx +++ b/docs/pull-requests/examples.mdx @@ -109,19 +109,21 @@ Link PRs to a board and assign two reviewers: Ensure the project has a column named **"In Review"** (or use your actual column name). -## Example: Images on PR comments +## Deprecated image compatibility -Enable images in PR comments and set URLs for feature PRs: +Existing workflows may still contain image inputs such as: ```yaml - uses: vypdev/copilot@v3 with: token: ${{ secrets.PAT }} - images-on-pull-request: true + images-on-pull-request: false images-pull-request-feature: "https://example.com/images/feature-pr.png" ``` -Other branch types: `images-pull-request-bugfix`, `images-pull-request-docs`, `images-pull-request-chore`, `images-pull-request-hotfix`, `images-pull-request-release`. See [Configuration](/configuration). +These inputs are accepted temporarily, but semantic comments do not use them. +New workflows should omit the toggle and all image pools. See +[Configuration](/configuration). ## Next steps diff --git a/docs/pull-requests/workflow-setup.mdx b/docs/pull-requests/workflow-setup.mdx index c9170f274..7fde330e4 100644 --- a/docs/pull-requests/workflow-setup.mdx +++ b/docs/pull-requests/workflow-setup.mdx @@ -90,7 +90,7 @@ jobs: Set the optional Repository Variable `COPILOT_BOT_LOGIN` to the PAT bot's login to skip bot-authored event jobs before a runner starts. The expression is generic and optional; the authenticated in-Action admission remains the fallback. This workflow is event-driven and does not require or install a `workflow_run` trigger. Consumers may add private, explicitly scoped workflow chaining in their own repositories. -2. **PR–issue linking:** The action infers the **issue number** from the PR branch name (e.g. `feature/123-add-login` → issue `123`) and **links the PR to that issue** (and posts a comment on the PR when configured). +2. **PR–issue linking:** The action infers the **issue number** from the PR branch name (e.g. `feature/123-add-login` → issue `123`) and **links the PR to that issue**. Routine linkage uses GitHub's native linked-PR UI and does not post a recap comment. 3. **Project linking:** If `project-ids` is set, the PR is added to those projects and moved to the configured column (e.g. "In Progress"). See [Capabilities](/pull-requests/capabilities). @@ -100,7 +100,7 @@ jobs: 6. **AI PR description:** When `ai-pull-request-description-mode` is not `disabled` and the selected agent CLI is configured, the action can generate or update the PR description from the issue and the branch diff according to the selected policy. See [AI PR description](/pull-requests/ai-description). -7. **Comments and images:** The action can post a comment on the PR with optional images per branch type (feature, bugfix, etc.). See [Capabilities](/pull-requests/capabilities). +7. **Conversation UX:** The action publishes only purpose-specific replies, one current Bugbot card, and bounded inline findings. Routine metadata changes do not post a generic action summary, and decorative image inputs are deprecated. See [Capabilities](/pull-requests/capabilities). 8. **Bugbot review:** On `opened` and `reopened`, the reviewer role analyzes the full PR. On `synchronize`, it analyzes the exact new commit range and reconciles every open finding against current code. Metadata-only edits do not invoke the agent. Active findings move the PR to `state:changes-requested` and `state:awaiting-issue-author`; a clean review moves it to `state:ready` and `state:awaiting-maintainer`. During the run, `state:ai-processing` may coexist with those labels and is removed when the agent finishes. Configure `reviewer-provider`, `reviewer-model-provider`, `reviewer-model`, `reviewer-effort`, and `reviewer-executable` only when the reviewer should differ from the base agent. diff --git a/docs/single-actions/available-actions.mdx b/docs/single-actions/available-actions.mdx index d6abb7da0..116c5a2e2 100644 --- a/docs/single-actions/available-actions.mdx +++ b/docs/single-actions/available-actions.mdx @@ -13,9 +13,9 @@ These actions need **`single-action-issue`** set to the issue number. The workfl | Action | Required inputs | Description | When to use | |--------|-----------------|-------------|-------------| -| **`check_progress_action`** | `single-action-issue` | Runs **progress check** on demand. The configured agent compares the issue description with the branch diff and updates the **progress** label (0–100%) on the issue and on any open PR for that branch. | Progress is normally updated on every **push** (commit workflow). Use this to re-run without pushing, or when you don’t use the push workflow. | +| **`check_progress_action`** | `single-action-issue` | Runs **progress check** on demand. The configured agent compares the issue description with the branch diff, updates the **progress** label (0–100%) on the issue and any open PR for that branch, and creates or updates one progress card. | Progress is normally updated on every **push** (commit workflow). Use this to re-run without pushing, or when you don’t use the push workflow. | | **`detect_potential_problems_action`** | `single-action-issue` | **Bugbot:** the configured agent analyzes the branch vs base and reports findings on the issue when no PR exists, or in one summarized review on an open PR; updates stored findings and resolves PR threads when findings are fixed. | Same as push-time Bugbot but on demand. See [Bugbot](/bugbot). | -| **`recommend_steps_action`** | `single-action-issue` | Uses the configured agent's analysis role to recommend **implementation steps** from the issue description; **posts a comment** on the issue with the steps. | When you want a one-off suggestion for how to implement the issue. | +| **`recommend_steps_action`** | `single-action-issue` | Uses the configured agent's analysis role to recommend **implementation steps** from the issue description and creates or updates one bounded plan card. | When you want a one-off suggestion for how to implement the issue. | | **`publish_issue_comment`** | `single-action-issue`, `single-action-message` | Creates a Markdown comment. With `single-action-comment-id`, it replaces that issue comment by default; set `single-action-comment-mode: append` to preserve its current content and append the message. | Reusable workflow notifications such as release or hotfix failures. | ## Actions that do not require an issue diff --git a/specs/CATALOG.md b/specs/CATALOG.md index 0f5da7365..8901a3e1a 100644 --- a/specs/CATALOG.md +++ b/specs/CATALOG.md @@ -10,11 +10,11 @@ debt or convert unknown historic intent into a design decision. | Capability ID | Status | Scope | Primary SDD | Evidence | |---|---|---|---|---| -| `github-communication-experience` | Proposed | English-default, localized, semantic, bounded, and idempotent product messages across GitHub and repository-aware operator surfaces | [Semantic GitHub communication and repository localization](./semantic-github-publication-and-notification.md) + 1 companion | 51 paths · 2026-09-14 | +| `github-communication-experience` | Proposed | English-default, localized, semantic, bounded, and idempotent product messages across GitHub and repository-aware operator surfaces | [Semantic GitHub communication and repository localization](./semantic-github-publication-and-notification.md) + 1 companion | 75 paths · 2026-09-14 | | `release-orchestration` | Implemented | Release and hotfix promotion, publication, reconciliation, and durable recovery | [Configurable production-first release orchestration](./configurable-release-orchestration.md) + 2 companion | 41 paths · 2026-09-12 | | `merge-queue-readiness` | Implemented | Fail-closed validation of required checks and merge-group workflow support | [Merge queue readiness and effective target rules](./merge-queue-readiness.md) | 15 paths · 2026-09-11 | | `bugbot-review-state-reconciliation` | Implemented | Reconcile review snapshots, findings, threads, comments, and check conclusions | [Bugbot review-state reconciliation](./bugbot-review-state-reconciliation.md) | 39 paths · 2026-09-13 | -| `execution-lifecycle` | Implemented | Shared GitHub Action lifecycle from event admission through durable user-facing results | [Execution admission, queueing, routing, and result publication](./execution-admission-queue-and-publication.md) + 3 companion | 81 paths · 2026-09-14 | +| `execution-lifecycle` | Implemented | Shared GitHub Action lifecycle from event admission through durable user-facing results | [Execution admission, queueing, routing, and result publication](./execution-admission-queue-and-publication.md) + 3 companion | 82 paths · 2026-09-14 | | `architecture-quality-hardening` | Implemented | Close verified concurrency, error-contract, context-coupling, fan-out, setup/doctor, and provider-policy risks in dependency order | [Architecture quality and scalability hardening](./architecture-quality-and-scalability-hardening.md) + 1 companion | 70 paths · 2026-09-14 | | `setup-and-doctor` | Implemented | Plan, validate, provision, and audit a repository installation without exposing credentials | [Setup, configuration, credentials, and doctor](./setup-configuration-credentials-and-doctor.md) + 1 companion | 39 paths · 2026-09-12 | | `managed-issue-lifecycle` | As-built baseline | Convert typed issues into traceable work branches, project state, and lifecycle state | [Managed issue and branch lifecycle](./managed-issue-and-branch-lifecycle.md) | 21 paths · 2026-09-13 | @@ -34,9 +34,9 @@ debt or convert unknown historic intent into a design decision. - Specifications: [`specs/semantic-github-publication-and-notification.md`](./semantic-github-publication-and-notification.md) · [`specs/repository-locale-and-localization.md`](./repository-locale-and-localization.md) - Workflows: [`.github/workflows/copilot_issue.yml`](../.github/workflows/copilot_issue.yml) · [`.github/workflows/copilot_issue_comment.yml`](../.github/workflows/copilot_issue_comment.yml) · [`.github/workflows/copilot_pull_request.yml`](../.github/workflows/copilot_pull_request.yml) · [`.github/workflows/copilot_pull_request_comment.yml`](../.github/workflows/copilot_pull_request_comment.yml) · [`.github/workflows/copilot_commit.yml`](../.github/workflows/copilot_commit.yml) · [`.github/workflows/copilot_deployment_orchestration.yml`](../.github/workflows/copilot_deployment_orchestration.yml) - Entrypoints: [`src/actions/github_action.ts`](../src/actions/github_action.ts) · [`src/actions/github_action_completion.ts`](../src/actions/github_action_completion.ts) · [`src/cli.ts`](../src/cli.ts) -- Core code: [`src/data/model/locale.ts`](../src/data/model/locale.ts) · [`src/actions/github_action_locale_inputs.ts`](../src/actions/github_action_locale_inputs.ts) · [`src/application/usecases/steps/common/comment_language_translation_workflow.ts`](../src/application/usecases/steps/common/comment_language_translation_workflow.ts) · [`src/application/policies/comment_translation_policy.ts`](../src/application/policies/comment_translation_policy.ts) · [`src/application/usecases/comment_automation_use_case.ts`](../src/application/usecases/comment_automation_use_case.ts) · [`src/application/usecases/steps/common/publish_resume_workflow.ts`](../src/application/usecases/steps/common/publish_resume_workflow.ts) · [`src/application/policies/result_publication_policy.ts`](../src/application/policies/result_publication_policy.ts) · [`src/application/policies/result_publication_presentation_policy.ts`](../src/application/policies/result_publication_presentation_policy.ts) · [`src/application/policies/result_publication_sections_policy.ts`](../src/application/policies/result_publication_sections_policy.ts) · [`src/application/policies/action_summary_policy.ts`](../src/application/policies/action_summary_policy.ts) · [`src/application/policies/branch_sync_notification_policy.ts`](../src/application/policies/branch_sync_notification_policy.ts) · [`src/application/policies/bugbot_review_presentation_policy.ts`](../src/application/policies/bugbot_review_presentation_policy.ts) · [`src/application/policies/deployment_presentation_policy.ts`](../src/application/policies/deployment_presentation_policy.ts) · [`src/application/usecases/actions/recommend_steps_workflow.ts`](../src/application/usecases/actions/recommend_steps_workflow.ts) · [`src/application/usecases/actions/check_progress_workflow.ts`](../src/application/usecases/actions/check_progress_workflow.ts) · [`src/data/repository/issue/issue_content_repository.ts`](../src/data/repository/issue/issue_content_repository.ts) -- Tests: [`src/actions/__tests__/configuration_builders.test.ts`](../src/actions/__tests__/configuration_builders.test.ts) · [`src/actions/__tests__/github_action_completion.test.ts`](../src/actions/__tests__/github_action_completion.test.ts) · [`src/application/policies/__tests__/comment_translation_policy.test.ts`](../src/application/policies/__tests__/comment_translation_policy.test.ts) · [`src/application/policies/__tests__/result_publication_policy.test.ts`](../src/application/policies/__tests__/result_publication_policy.test.ts) · [`src/application/policies/__tests__/action_summary_policy.test.ts`](../src/application/policies/__tests__/action_summary_policy.test.ts) · [`src/application/policies/__tests__/branch_sync_notification_policy.test.ts`](../src/application/policies/__tests__/branch_sync_notification_policy.test.ts) · [`src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts`](../src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts) · [`src/application/policies/__tests__/deployment_presentation_policy.test.ts`](../src/application/policies/__tests__/deployment_presentation_policy.test.ts) · [`src/application/usecases/steps/common/__tests__/comment_language_translation_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/comment_language_translation_workflow.test.ts) · [`src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts`](../src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts) · [`src/application/usecases/actions/__tests__/recommend_steps_use_case.test.ts`](../src/application/usecases/actions/__tests__/recommend_steps_use_case.test.ts) · [`src/application/usecases/actions/__tests__/check_progress_use_case.test.ts`](../src/application/usecases/actions/__tests__/check_progress_use_case.test.ts) · [`src/application/usecases/__tests__/comment_automation_use_case.test.ts`](../src/application/usecases/__tests__/comment_automation_use_case.test.ts) -- User documentation: [`docs/configuration.mdx`](../docs/configuration.mdx) · [`docs/configuration-checklist.mdx`](../docs/configuration-checklist.mdx) · [`docs/features.mdx`](../docs/features.mdx) · [`docs/issues/configuration.mdx`](../docs/issues/configuration.mdx) · [`docs/issues/comment-commands.mdx`](../docs/issues/comment-commands.mdx) · [`docs/issues/notifications-and-auto-close.mdx`](../docs/issues/notifications-and-auto-close.mdx) · [`docs/issues/deployment-orchestration.mdx`](../docs/issues/deployment-orchestration.mdx) · [`docs/pull-requests/configuration.mdx`](../docs/pull-requests/configuration.mdx) · [`docs/pull-requests/capabilities.mdx`](../docs/pull-requests/capabilities.mdx) · [`docs/bugbot/configuration.mdx`](../docs/bugbot/configuration.mdx) · [`docs/bugbot/finding-publication.mdx`](../docs/bugbot/finding-publication.mdx) · [`docs/single-actions/configuration.mdx`](../docs/single-actions/configuration.mdx) · [`docs/development/architecture.mdx`](../docs/development/architecture.mdx) +- Core code: [`src/data/model/locale.ts`](../src/data/model/locale.ts) · [`src/actions/github_action_locale_inputs.ts`](../src/actions/github_action_locale_inputs.ts) · [`src/application/usecases/steps/common/comment_language_translation_workflow.ts`](../src/application/usecases/steps/common/comment_language_translation_workflow.ts) · [`src/application/policies/comment_translation_policy.ts`](../src/application/policies/comment_translation_policy.ts) · [`src/application/usecases/comment_automation_use_case.ts`](../src/application/usecases/comment_automation_use_case.ts) · [`src/application/usecases/steps/common/publish_resume_workflow.ts`](../src/application/usecases/steps/common/publish_resume_workflow.ts) · [`src/domain/github_publication.ts`](../src/domain/github_publication.ts) · [`src/application/policies/publication_identity_policy.ts`](../src/application/policies/publication_identity_policy.ts) · [`src/application/policies/publication_message_catalog.ts`](../src/application/policies/publication_message_catalog.ts) · [`src/application/policies/semantic_result_publication_policy.ts`](../src/application/policies/semantic_result_publication_policy.ts) · [`src/application/usecases/steps/common/status_card_publication_workflow.ts`](../src/application/usecases/steps/common/status_card_publication_workflow.ts) · [`src/application/usecases/steps/common/reply_publication_workflow.ts`](../src/application/usecases/steps/common/reply_publication_workflow.ts) · [`src/architecture/github_publication_mutation_baseline.json`](../src/architecture/github_publication_mutation_baseline.json) · [`src/application/policies/action_summary_policy.ts`](../src/application/policies/action_summary_policy.ts) · [`src/application/policies/branch_sync_notification_policy.ts`](../src/application/policies/branch_sync_notification_policy.ts) · [`src/application/policies/bugbot_review_presentation_policy.ts`](../src/application/policies/bugbot_review_presentation_policy.ts) · [`src/application/policies/deployment_presentation_policy.ts`](../src/application/policies/deployment_presentation_policy.ts) · [`src/application/usecases/actions/recommend_steps_workflow.ts`](../src/application/usecases/actions/recommend_steps_workflow.ts) · [`src/application/usecases/actions/check_progress_workflow.ts`](../src/application/usecases/actions/check_progress_workflow.ts) · [`src/data/repository/issue/issue_content_repository.ts`](../src/data/repository/issue/issue_content_repository.ts) +- Tests: [`src/actions/__tests__/configuration_builders.test.ts`](../src/actions/__tests__/configuration_builders.test.ts) · [`src/actions/__tests__/github_action_completion.test.ts`](../src/actions/__tests__/github_action_completion.test.ts) · [`src/application/policies/__tests__/comment_translation_policy.test.ts`](../src/application/policies/__tests__/comment_translation_policy.test.ts) · [`src/domain/__tests__/github_publication.test.ts`](../src/domain/__tests__/github_publication.test.ts) · [`src/application/policies/__tests__/publication_identity_policy.test.ts`](../src/application/policies/__tests__/publication_identity_policy.test.ts) · [`src/application/policies/__tests__/publication_message_catalog.test.ts`](../src/application/policies/__tests__/publication_message_catalog.test.ts) · [`src/application/policies/__tests__/semantic_result_publication_policy.test.ts`](../src/application/policies/__tests__/semantic_result_publication_policy.test.ts) · [`src/application/policies/__tests__/action_summary_policy.test.ts`](../src/application/policies/__tests__/action_summary_policy.test.ts) · [`src/application/policies/__tests__/branch_sync_notification_policy.test.ts`](../src/application/policies/__tests__/branch_sync_notification_policy.test.ts) · [`src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts`](../src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts) · [`src/application/policies/__tests__/deployment_presentation_policy.test.ts`](../src/application/policies/__tests__/deployment_presentation_policy.test.ts) · [`src/application/usecases/steps/common/__tests__/comment_language_translation_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/comment_language_translation_workflow.test.ts) · [`src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts`](../src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts) · [`src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts) · [`src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts`](../src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts) · [`src/architecture/__tests__/github_publication_boundaries.test.ts`](../src/architecture/__tests__/github_publication_boundaries.test.ts) · [`src/tooling/__tests__/validate_workflow_contract.test.ts`](../src/tooling/__tests__/validate_workflow_contract.test.ts) · [`src/application/usecases/actions/__tests__/recommend_steps_use_case.test.ts`](../src/application/usecases/actions/__tests__/recommend_steps_use_case.test.ts) · [`src/application/usecases/actions/__tests__/check_progress_use_case.test.ts`](../src/application/usecases/actions/__tests__/check_progress_use_case.test.ts) · [`src/application/usecases/__tests__/comment_automation_use_case.test.ts`](../src/application/usecases/__tests__/comment_automation_use_case.test.ts) +- User documentation: [`docs/configuration.mdx`](../docs/configuration.mdx) · [`docs/configuration-checklist.mdx`](../docs/configuration-checklist.mdx) · [`docs/features.mdx`](../docs/features.mdx) · [`docs/issues/configuration.mdx`](../docs/issues/configuration.mdx) · [`docs/issues/comment-commands.mdx`](../docs/issues/comment-commands.mdx) · [`docs/issues/notifications-and-auto-close.mdx`](../docs/issues/notifications-and-auto-close.mdx) · [`docs/issues/branch-synchronization.mdx`](../docs/issues/branch-synchronization.mdx) · [`docs/issues/type/feature.mdx`](../docs/issues/type/feature.mdx) · [`docs/issues/type/bugfix.mdx`](../docs/issues/type/bugfix.mdx) · [`docs/issues/type/docs.mdx`](../docs/issues/type/docs.mdx) · [`docs/issues/type/chore.mdx`](../docs/issues/type/chore.mdx) · [`docs/issues/type/hotfix.mdx`](../docs/issues/type/hotfix.mdx) · [`docs/issues/type/release.mdx`](../docs/issues/type/release.mdx) · [`docs/issues/deployment-orchestration.mdx`](../docs/issues/deployment-orchestration.mdx) · [`docs/pull-requests/configuration.mdx`](../docs/pull-requests/configuration.mdx) · [`docs/pull-requests/capabilities.mdx`](../docs/pull-requests/capabilities.mdx) · [`docs/pull-requests/workflow-setup.mdx`](../docs/pull-requests/workflow-setup.mdx) · [`docs/pull-requests/examples.mdx`](../docs/pull-requests/examples.mdx) · [`docs/bugbot/configuration.mdx`](../docs/bugbot/configuration.mdx) · [`docs/bugbot/finding-publication.mdx`](../docs/bugbot/finding-publication.mdx) · [`docs/bugbot/detection.mdx`](../docs/bugbot/detection.mdx) · [`docs/bugbot/failure-scenarios.mdx`](../docs/bugbot/failure-scenarios.mdx) · [`docs/bugbot/examples.mdx`](../docs/bugbot/examples.mdx) · [`docs/single-actions/configuration.mdx`](../docs/single-actions/configuration.mdx) · [`docs/single-actions/available-actions.mdx`](../docs/single-actions/available-actions.mdx) · [`docs/development/architecture.mdx`](../docs/development/architecture.mdx) ### `release-orchestration` — Configurable production-first release orchestration @@ -78,7 +78,7 @@ debt or convert unknown historic intent into a design decision. - Specifications: [`specs/execution-admission-queue-and-publication.md`](./execution-admission-queue-and-publication.md) · [`specs/execution-error-and-context-hardening.md`](./execution-error-and-context-hardening.md) · [`specs/execution-boundary-closure-audit.md`](./execution-boundary-closure-audit.md) · [`specs/push-and-single-action-context-hardening.md`](./push-and-single-action-context-hardening.md) - Workflows: [`.github/workflows/copilot_issue.yml`](../.github/workflows/copilot_issue.yml) · [`.github/workflows/copilot_issue_comment.yml`](../.github/workflows/copilot_issue_comment.yml) · [`.github/workflows/copilot_pull_request.yml`](../.github/workflows/copilot_pull_request.yml) · [`.github/workflows/copilot_pull_request_comment.yml`](../.github/workflows/copilot_pull_request_comment.yml) · [`.github/workflows/copilot_commit.yml`](../.github/workflows/copilot_commit.yml) - Entrypoints: [`src/actions/github_action.ts`](../src/actions/github_action.ts) · [`src/actions/common_action.ts`](../src/actions/common_action.ts) -- Core code: [`scripts/coverage-budgets.json`](../scripts/coverage-budgets.json) · [`scripts/validate-coverage-budgets.cjs`](../scripts/validate-coverage-budgets.cjs) · [`src/actions/main_run_lifecycle.ts`](../src/actions/main_run_lifecycle.ts) · [`src/actions/main_run_route.ts`](../src/actions/main_run_route.ts) · [`src/actions/github_action_completion.ts`](../src/actions/github_action_completion.ts) · [`src/api.ts`](../src/api.ts) · [`src/data/model/application_error.ts`](../src/data/model/application_error.ts) · [`src/application/errors/application_error.ts`](../src/application/errors/application_error.ts) · [`src/application/errors/application_error_context.ts`](../src/application/errors/application_error_context.ts) · [`src/application/policies/application_error_presentation_policy.ts`](../src/application/policies/application_error_presentation_policy.ts) · [`src/application/policies/bugbot_result_finding_state_projection_policy.ts`](../src/application/policies/bugbot_result_finding_state_projection_policy.ts) · [`src/application/policies/bugbot_telemetry_projection_policy.ts`](../src/application/policies/bugbot_telemetry_projection_policy.ts) · [`src/application/policies/action_summary_policy.ts`](../src/application/policies/action_summary_policy.ts) · [`src/application/policies/copilot_evidence_policy.ts`](../src/application/policies/copilot_evidence_policy.ts) · [`src/application/policies/lifecycle_state_policy.ts`](../src/application/policies/lifecycle_state_policy.ts) · [`src/application/policies/status_command_policy.ts`](../src/application/policies/status_command_policy.ts) · [`src/application/policies/github_execution_admission_policy.ts`](../src/application/policies/github_execution_admission_policy.ts) · [`src/application/policies/workflow_queue_policy.ts`](../src/application/policies/workflow_queue_policy.ts) · [`src/application/usecases/actions/lifecycle_synchronization_context.ts`](../src/application/usecases/actions/lifecycle_synchronization_context.ts) · [`src/application/usecases/actions/synchronize_lifecycle_state_use_case.ts`](../src/application/usecases/actions/synchronize_lifecycle_state_use_case.ts) · [`src/application/usecases/steps/commit/bugbot/bugbot_review_operation_context.ts`](../src/application/usecases/steps/commit/bugbot/bugbot_review_operation_context.ts) · [`src/application/policies/result_publication_policy.ts`](../src/application/policies/result_publication_policy.ts) · [`src/application/usecases/comment_automation_context.ts`](../src/application/usecases/comment_automation_context.ts) · [`src/application/usecases/issue_workflow_context.ts`](../src/application/usecases/issue_workflow_context.ts) · [`src/application/usecases/pull_request_workflow_context.ts`](../src/application/usecases/pull_request_workflow_context.ts) · [`src/application/usecases/push_single_action_contexts.ts`](../src/application/usecases/push_single_action_contexts.ts) · [`src/application/usecases/steps/common/check_permissions_workflow.ts`](../src/application/usecases/steps/common/check_permissions_workflow.ts) · [`src/application/usecases/steps/common/comment_language_translation_workflow.ts`](../src/application/usecases/steps/common/comment_language_translation_workflow.ts) · [`src/application/usecases/steps/common/think_workflow.ts`](../src/application/usecases/steps/common/think_workflow.ts) · [`src/application/usecases/steps/common/update_title_workflow.ts`](../src/application/usecases/steps/common/update_title_workflow.ts) · [`src/application/usecases/steps/common/project_content_link_workflow.ts`](../src/application/usecases/steps/common/project_content_link_workflow.ts) · [`src/application/usecases/steps/common/publish_resume_workflow.ts`](../src/application/usecases/steps/common/publish_resume_workflow.ts) · [`src/application/usecases/steps/common/store_configuration_use_case.ts`](../src/application/usecases/steps/common/store_configuration_use_case.ts) · [`src/infrastructure/composition/shared_capability_port_binding.ts`](../src/infrastructure/composition/shared_capability_port_binding.ts) · [`src/infrastructure/composition/lifecycle_capability_port_binding.ts`](../src/infrastructure/composition/lifecycle_capability_port_binding.ts) · [`src/infrastructure/composition/lifecycle_state_composition_root.ts`](../src/infrastructure/composition/lifecycle_state_composition_root.ts) · [`src/infrastructure/composition/push_single_action_capability_port_binding.ts`](../src/infrastructure/composition/push_single_action_capability_port_binding.ts) · [`src/infrastructure/composition/main_run_route_composition_root.ts`](../src/infrastructure/composition/main_run_route_composition_root.ts) +- Core code: [`scripts/coverage-budgets.json`](../scripts/coverage-budgets.json) · [`scripts/validate-coverage-budgets.cjs`](../scripts/validate-coverage-budgets.cjs) · [`src/actions/main_run_lifecycle.ts`](../src/actions/main_run_lifecycle.ts) · [`src/actions/main_run_route.ts`](../src/actions/main_run_route.ts) · [`src/actions/github_action_completion.ts`](../src/actions/github_action_completion.ts) · [`src/api.ts`](../src/api.ts) · [`src/data/model/application_error.ts`](../src/data/model/application_error.ts) · [`src/application/errors/application_error.ts`](../src/application/errors/application_error.ts) · [`src/application/errors/application_error_context.ts`](../src/application/errors/application_error_context.ts) · [`src/application/policies/application_error_presentation_policy.ts`](../src/application/policies/application_error_presentation_policy.ts) · [`src/application/policies/bugbot_result_finding_state_projection_policy.ts`](../src/application/policies/bugbot_result_finding_state_projection_policy.ts) · [`src/application/policies/bugbot_telemetry_projection_policy.ts`](../src/application/policies/bugbot_telemetry_projection_policy.ts) · [`src/application/policies/action_summary_policy.ts`](../src/application/policies/action_summary_policy.ts) · [`src/application/policies/copilot_evidence_policy.ts`](../src/application/policies/copilot_evidence_policy.ts) · [`src/application/policies/lifecycle_state_policy.ts`](../src/application/policies/lifecycle_state_policy.ts) · [`src/application/policies/status_command_policy.ts`](../src/application/policies/status_command_policy.ts) · [`src/application/policies/github_execution_admission_policy.ts`](../src/application/policies/github_execution_admission_policy.ts) · [`src/application/policies/workflow_queue_policy.ts`](../src/application/policies/workflow_queue_policy.ts) · [`src/application/usecases/actions/lifecycle_synchronization_context.ts`](../src/application/usecases/actions/lifecycle_synchronization_context.ts) · [`src/application/usecases/actions/synchronize_lifecycle_state_use_case.ts`](../src/application/usecases/actions/synchronize_lifecycle_state_use_case.ts) · [`src/application/usecases/steps/commit/bugbot/bugbot_review_operation_context.ts`](../src/application/usecases/steps/commit/bugbot/bugbot_review_operation_context.ts) · [`src/application/policies/semantic_result_publication_policy.ts`](../src/application/policies/semantic_result_publication_policy.ts) · [`src/application/usecases/steps/common/status_card_publication_workflow.ts`](../src/application/usecases/steps/common/status_card_publication_workflow.ts) · [`src/application/usecases/comment_automation_context.ts`](../src/application/usecases/comment_automation_context.ts) · [`src/application/usecases/issue_workflow_context.ts`](../src/application/usecases/issue_workflow_context.ts) · [`src/application/usecases/pull_request_workflow_context.ts`](../src/application/usecases/pull_request_workflow_context.ts) · [`src/application/usecases/push_single_action_contexts.ts`](../src/application/usecases/push_single_action_contexts.ts) · [`src/application/usecases/steps/common/check_permissions_workflow.ts`](../src/application/usecases/steps/common/check_permissions_workflow.ts) · [`src/application/usecases/steps/common/comment_language_translation_workflow.ts`](../src/application/usecases/steps/common/comment_language_translation_workflow.ts) · [`src/application/usecases/steps/common/think_workflow.ts`](../src/application/usecases/steps/common/think_workflow.ts) · [`src/application/usecases/steps/common/update_title_workflow.ts`](../src/application/usecases/steps/common/update_title_workflow.ts) · [`src/application/usecases/steps/common/project_content_link_workflow.ts`](../src/application/usecases/steps/common/project_content_link_workflow.ts) · [`src/application/usecases/steps/common/publish_resume_workflow.ts`](../src/application/usecases/steps/common/publish_resume_workflow.ts) · [`src/application/usecases/steps/common/store_configuration_use_case.ts`](../src/application/usecases/steps/common/store_configuration_use_case.ts) · [`src/infrastructure/composition/shared_capability_port_binding.ts`](../src/infrastructure/composition/shared_capability_port_binding.ts) · [`src/infrastructure/composition/lifecycle_capability_port_binding.ts`](../src/infrastructure/composition/lifecycle_capability_port_binding.ts) · [`src/infrastructure/composition/lifecycle_state_composition_root.ts`](../src/infrastructure/composition/lifecycle_state_composition_root.ts) · [`src/infrastructure/composition/push_single_action_capability_port_binding.ts`](../src/infrastructure/composition/push_single_action_capability_port_binding.ts) · [`src/infrastructure/composition/main_run_route_composition_root.ts`](../src/infrastructure/composition/main_run_route_composition_root.ts) - Tests: [`src/tooling/__tests__/coverage_budget_validator.test.ts`](../src/tooling/__tests__/coverage_budget_validator.test.ts) · [`src/__tests__/api.test.ts`](../src/__tests__/api.test.ts) · [`src/actions/__tests__/github_action.test.ts`](../src/actions/__tests__/github_action.test.ts) · [`src/actions/__tests__/github_action_completion.test.ts`](../src/actions/__tests__/github_action_completion.test.ts) · [`src/application/errors/__tests__/application_error.test.ts`](../src/application/errors/__tests__/application_error.test.ts) · [`src/application/policies/__tests__/application_error_presentation_policy.test.ts`](../src/application/policies/__tests__/application_error_presentation_policy.test.ts) · [`src/application/policies/__tests__/bugbot_result_finding_state_projection_policy.test.ts`](../src/application/policies/__tests__/bugbot_result_finding_state_projection_policy.test.ts) · [`src/application/policies/__tests__/bugbot_telemetry_projection_policy.test.ts`](../src/application/policies/__tests__/bugbot_telemetry_projection_policy.test.ts) · [`src/application/policies/__tests__/action_summary_policy.test.ts`](../src/application/policies/__tests__/action_summary_policy.test.ts) · [`src/application/policies/__tests__/copilot_evidence_policy.test.ts`](../src/application/policies/__tests__/copilot_evidence_policy.test.ts) · [`src/application/policies/__tests__/lifecycle_state_policy.test.ts`](../src/application/policies/__tests__/lifecycle_state_policy.test.ts) · [`src/application/policies/__tests__/status_command_policy.test.ts`](../src/application/policies/__tests__/status_command_policy.test.ts) · [`src/application/usecases/actions/__tests__/lifecycle_synchronization_context.test.ts`](../src/application/usecases/actions/__tests__/lifecycle_synchronization_context.test.ts) · [`src/application/usecases/actions/__tests__/synchronize_lifecycle_state_use_case.test.ts`](../src/application/usecases/actions/__tests__/synchronize_lifecycle_state_use_case.test.ts) · [`src/application/usecases/actions/__tests__/lifecycle_event_replay.integration.test.ts`](../src/application/usecases/actions/__tests__/lifecycle_event_replay.integration.test.ts) · [`src/architecture/__tests__/execution_import_ratchet.test.ts`](../src/architecture/__tests__/execution_import_ratchet.test.ts) · [`src/architecture/__tests__/raw_error_logging_boundaries.test.ts`](../src/architecture/__tests__/raw_error_logging_boundaries.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/bugbot_review_operation_context.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/bugbot_review_operation_context.test.ts) · [`src/application/usecases/steps/common/__tests__/shared_capability_context_projection.test.ts`](../src/application/usecases/steps/common/__tests__/shared_capability_context_projection.test.ts) · [`src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts`](../src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts) · [`src/infrastructure/composition/__tests__/shared_capability_port_binding.test.ts`](../src/infrastructure/composition/__tests__/shared_capability_port_binding.test.ts) · [`src/application/usecases/__tests__/issue_pull_request_context_projection.test.ts`](../src/application/usecases/__tests__/issue_pull_request_context_projection.test.ts) · [`src/infrastructure/composition/__tests__/lifecycle_capability_port_binding.test.ts`](../src/infrastructure/composition/__tests__/lifecycle_capability_port_binding.test.ts) · [`src/infrastructure/composition/__tests__/lifecycle_state_composition_root.test.ts`](../src/infrastructure/composition/__tests__/lifecycle_state_composition_root.test.ts) · [`src/application/usecases/__tests__/push_single_action_contexts.test.ts`](../src/application/usecases/__tests__/push_single_action_contexts.test.ts) · [`src/application/usecases/__tests__/single_action_workflow.test.ts`](../src/application/usecases/__tests__/single_action_workflow.test.ts) · [`src/infrastructure/composition/__tests__/push_single_action_capability_port_binding.test.ts`](../src/infrastructure/composition/__tests__/push_single_action_capability_port_binding.test.ts) · [`src/application/usecases/execution/__tests__/resolve_github_execution_admission_use_case.test.ts`](../src/application/usecases/execution/__tests__/resolve_github_execution_admission_use_case.test.ts) · [`src/application/usecases/workflow/__tests__/wait_for_previous_workflow_runs_use_case.test.ts`](../src/application/usecases/workflow/__tests__/wait_for_previous_workflow_runs_use_case.test.ts) - User documentation: [`docs/overview.mdx`](../docs/overview.mdx) · [`docs/bugbot/programmatic-api.mdx`](../docs/bugbot/programmatic-api.mdx) · [`docs/security-operations/operations/error-reference.mdx`](../docs/security-operations/operations/error-reference.mdx) · [`docs/security-operations/operations/troubleshooting.mdx`](../docs/security-operations/operations/troubleshooting.mdx) · [`docs/bugbot/quality-observability.mdx`](../docs/bugbot/quality-observability.mdx) · [`docs/bugbot/failure-scenarios.mdx`](../docs/bugbot/failure-scenarios.mdx) · [`docs/development/architecture.mdx`](../docs/development/architecture.mdx) diff --git a/specs/catalog.json b/specs/catalog.json index c3de0a864..dd550f230 100644 --- a/specs/catalog.json +++ b/specs/catalog.json @@ -32,9 +32,13 @@ "src/application/policies/comment_translation_policy.ts", "src/application/usecases/comment_automation_use_case.ts", "src/application/usecases/steps/common/publish_resume_workflow.ts", - "src/application/policies/result_publication_policy.ts", - "src/application/policies/result_publication_presentation_policy.ts", - "src/application/policies/result_publication_sections_policy.ts", + "src/domain/github_publication.ts", + "src/application/policies/publication_identity_policy.ts", + "src/application/policies/publication_message_catalog.ts", + "src/application/policies/semantic_result_publication_policy.ts", + "src/application/usecases/steps/common/status_card_publication_workflow.ts", + "src/application/usecases/steps/common/reply_publication_workflow.ts", + "src/architecture/github_publication_mutation_baseline.json", "src/application/policies/action_summary_policy.ts", "src/application/policies/branch_sync_notification_policy.ts", "src/application/policies/bugbot_review_presentation_policy.ts", @@ -47,13 +51,20 @@ "src/actions/__tests__/configuration_builders.test.ts", "src/actions/__tests__/github_action_completion.test.ts", "src/application/policies/__tests__/comment_translation_policy.test.ts", - "src/application/policies/__tests__/result_publication_policy.test.ts", + "src/domain/__tests__/github_publication.test.ts", + "src/application/policies/__tests__/publication_identity_policy.test.ts", + "src/application/policies/__tests__/publication_message_catalog.test.ts", + "src/application/policies/__tests__/semantic_result_publication_policy.test.ts", "src/application/policies/__tests__/action_summary_policy.test.ts", "src/application/policies/__tests__/branch_sync_notification_policy.test.ts", "src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts", "src/application/policies/__tests__/deployment_presentation_policy.test.ts", "src/application/usecases/steps/common/__tests__/comment_language_translation_workflow.test.ts", "src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts", + "src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts", + "src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts", + "src/architecture/__tests__/github_publication_boundaries.test.ts", + "src/tooling/__tests__/validate_workflow_contract.test.ts", "src/application/usecases/actions/__tests__/recommend_steps_use_case.test.ts", "src/application/usecases/actions/__tests__/check_progress_use_case.test.ts", "src/application/usecases/__tests__/comment_automation_use_case.test.ts" @@ -65,12 +76,25 @@ "docs/issues/configuration.mdx", "docs/issues/comment-commands.mdx", "docs/issues/notifications-and-auto-close.mdx", + "docs/issues/branch-synchronization.mdx", + "docs/issues/type/feature.mdx", + "docs/issues/type/bugfix.mdx", + "docs/issues/type/docs.mdx", + "docs/issues/type/chore.mdx", + "docs/issues/type/hotfix.mdx", + "docs/issues/type/release.mdx", "docs/issues/deployment-orchestration.mdx", "docs/pull-requests/configuration.mdx", "docs/pull-requests/capabilities.mdx", + "docs/pull-requests/workflow-setup.mdx", + "docs/pull-requests/examples.mdx", "docs/bugbot/configuration.mdx", "docs/bugbot/finding-publication.mdx", + "docs/bugbot/detection.mdx", + "docs/bugbot/failure-scenarios.mdx", + "docs/bugbot/examples.mdx", "docs/single-actions/configuration.mdx", + "docs/single-actions/available-actions.mdx", "docs/development/architecture.mdx" ] }, @@ -280,7 +304,8 @@ "src/application/usecases/actions/lifecycle_synchronization_context.ts", "src/application/usecases/actions/synchronize_lifecycle_state_use_case.ts", "src/application/usecases/steps/commit/bugbot/bugbot_review_operation_context.ts", - "src/application/policies/result_publication_policy.ts", + "src/application/policies/semantic_result_publication_policy.ts", + "src/application/usecases/steps/common/status_card_publication_workflow.ts", "src/application/usecases/comment_automation_context.ts", "src/application/usecases/issue_workflow_context.ts", "src/application/usecases/pull_request_workflow_context.ts", diff --git a/src/actions/__tests__/github_action_completion.test.ts b/src/actions/__tests__/github_action_completion.test.ts index ebe7189ee..46b5fbc3d 100644 --- a/src/actions/__tests__/github_action_completion.test.ts +++ b/src/actions/__tests__/github_action_completion.test.ts @@ -283,9 +283,7 @@ describe('finishGithubAction', () => { ); expect(mockSummaryPublish).toHaveBeenCalledWith(expect.stringContaining('UpdateTitleUseCase')); - expect(mockPublishInvoke).toHaveBeenCalledWith(expect.objectContaining({ - genericCommentMode: 'omit-metadata-only', - })); + expect(mockPublishInvoke).toHaveBeenCalledWith(expect.objectContaining({ locale: 'en-US' })); expect(mockEvidencePublish).not.toHaveBeenCalled(); }); @@ -312,9 +310,7 @@ describe('finishGithubAction', () => { { publish: mockSummaryPublish }, ); - expect(mockPublishInvoke).toHaveBeenCalledWith(expect.objectContaining({ - genericCommentMode: 'omit-metadata-only', - })); + expect(mockPublishInvoke).toHaveBeenCalledWith(expect.objectContaining({ locale: 'en-US' })); expect(mockSummaryPublish).toHaveBeenCalledWith(expect.stringContaining('Title normalization failed.')); expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('Title normalization failed.')); }); diff --git a/src/actions/github_action.ts b/src/actions/github_action.ts index 0a9bbda9a..36cd65127 100644 --- a/src/actions/github_action.ts +++ b/src/actions/github_action.ts @@ -3,7 +3,6 @@ import * as github from '@actions/github'; import { ConfigurationHandler } from '../manager/description/configuration_handler'; import { GitCliRepository } from '../data/repository/git_cli_repository'; import { createIssueContentCompositionRoot } from '../infrastructure/composition/issue_content_composition_root'; -import { createIssueNotificationRepository } from '../infrastructure/composition/issue_interaction_composition_root'; import { createProjectBoardCompositionRoot } from '../infrastructure/composition/project_board_composition_root'; import { finishGithubAction } from './github_action_completion'; import { getGithubActionInput } from './github_action_input'; @@ -24,7 +23,7 @@ import { createActorAuthorizationRepository } from '../infrastructure/compositio import { runAtApplicationErrorBoundary } from '../application/errors/application_error_context'; import { toApplicationError } from '../application/errors/application_error'; import { renderApplicationErrorText } from '../application/policies/application_error_presentation_policy'; -import { bindIssueNotification } from '../infrastructure/composition/shared_capability_port_binding'; +import { bindIssueCommentPublication } from '../infrastructure/composition/push_single_action_capability_port_binding'; export async function runGitHubAction(): Promise { const eventInputs = buildGithubActionEventInputs({ @@ -112,7 +111,7 @@ export async function runGitHubAction(): Promise { await finishGithubAction( execution, results, - bindIssueNotification(createIssueNotificationRepository(), repositoryBinding), + bindIssueCommentPublication(issueContentPort, repositoryBinding), { update: (context) => configurationHandler.update({ ...repositoryBinding, diff --git a/src/actions/github_action_completion.ts b/src/actions/github_action_completion.ts index 6242c6ead..5448d9a21 100644 --- a/src/actions/github_action_completion.ts +++ b/src/actions/github_action_completion.ts @@ -10,7 +10,6 @@ import { projectPublishResultContext } from '../application/usecases/steps/commo import { projectConfigurationPersistenceContext } from '../application/usecases/steps/common/store_configuration_use_case'; import { logInfo } from '../utils/logger'; -import { createLogReportAdapter } from '../infrastructure/logging/logger_adapter'; import { buildActionSummary } from '../application/policies/action_summary_policy'; import { lifecycleStateFromLabels } from '../domain/copilot_lifecycle'; import type { CopilotEvidencePort } from '../application/ports/copilot_evidence_ports'; @@ -41,7 +40,6 @@ export async function finishGithubAction( if (!dryRun && !execution.singleAction.isPublishIssueCommentAction && !ownsDeploymentPresentation) { const publicationFailure = await new PublishResultUseCase( issueNotificationPort, - createLogReportAdapter(), ).invoke(projectPublishResultContext(execution)); if (publicationFailure) results.push(publicationFailure); } else if (execution.singleAction.isPublishIssueCommentAction || ownsDeploymentPresentation) { diff --git a/src/application/policies/__tests__/branch_sync_notification_policy.test.ts b/src/application/policies/__tests__/branch_sync_notification_policy.test.ts index c5fa191ad..d51d9d135 100644 --- a/src/application/policies/__tests__/branch_sync_notification_policy.test.ts +++ b/src/application/policies/__tests__/branch_sync_notification_policy.test.ts @@ -56,6 +56,7 @@ describe("branch sync notification policy", () => { comparison: { aheadBy: 2, behindBy: 3 }, }); expect(stale).toContain(BRANCH_SYNC_STALE_MARKER); + expect(stale).toContain('topic="branch-sync" target="issue:42"'); expect(stale).toContain("3 commit(s) behind"); expect(stale).toContain("2 commit(s) not present"); expect(stale).toContain("/copilot sync-branch"); @@ -67,4 +68,14 @@ describe("branch sync notification policy", () => { expect(aligned).toContain("now contains"); expect(isStaleBranchSyncComment(aligned)).toBe(false); }); + + it('renders the same semantic branch states in Spanish', () => { + const stale = buildStaleBranchSyncComment({ + owner: 'org', repository: 'repo', dependency, + comparison: { aheadBy: 1, behindBy: 2 }, locale: 'es-MX', + }); + expect(stale).toContain('## Acción necesaria: sincroniza la rama'); + expect(stale).toContain('Ejecuta `/copilot sync-branch`'); + expect(buildAlignedBranchSyncComment(dependency, 'es-ES')).toContain('## Rama sincronizada'); + }); }); diff --git a/src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts b/src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts index 5f653f4a4..f4c4ebb39 100644 --- a/src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts +++ b/src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts @@ -45,11 +45,13 @@ describe('Bugbot review presentation', () => { it('renders one complete clean status card with navigation and no-action copy', () => { const body = renderBugbotStatusCard(projection(['fixed']), 'en-US', links); - expect(body).toContain('## 🤖 Bugbot status'); + expect(body).toContain('## Bugbot: review complete'); expect(body).toContain('No active findings'); - expect(body).toContain('No action required'); expect(body).toContain('[Verified commit]'); - expect(body).toContain('| Fixed | 1 |'); + expect(body).not.toContain('### Findings'); + expect(body).not.toContain('### Coverage'); + expect(body).not.toContain('Technical details'); + expect(body).toContain('topic="bugbot" target="pr:358" key="aggregate"'); expect(isBugbotStatusComment(body)).toBe(true); }); @@ -59,9 +61,9 @@ describe('Bugbot review presentation', () => { 'en-US', links, ); - expect(body).toContain('2 finding(s) require attention'); + expect(body).toContain('2 finding(s) need attention'); expect(body).toContain('/copilot fix all'); - expect(body).toContain('| Verification required | 1 |'); + expect(body).toContain('[ ] verification-required'); }); it('renders unknown state as explicit recovery work', () => { @@ -72,8 +74,8 @@ describe('Bugbot review presentation', () => { it('renders Spanish product copy while keeping machine markers neutral', () => { const body = renderBugbotStatusCard(projection(['fixed']), 'es-ES', links); - expect(body).toContain('## 🤖 Estado de Bugbot'); - expect(body).toContain('No se requiere ninguna acción'); + expect(body).toContain('## Bugbot: revisión completada'); + expect(body).toContain('No hay hallazgos activos'); expect(body).toContain('copilot-bugbot-status schema="1"'); }); @@ -125,16 +127,16 @@ describe('Bugbot review presentation', () => { expect(body).toContain('cannot declare the whole pull request clean'); expect(body).toContain('issue-comments: partial; retained=200, omitted=1'); - expect(body).toContain('Projection: partial'); + expect(body).toContain('Inspect the omitted items or reduce the PR scope'); + expect(body).toContain('Rerun the review only after changing'); + expect(body).not.toContain('Run `/copilot recheck`'); expect(body).not.toContain('No active findings'); expect(body).not.toContain('No action required'); }); it('renders empty and long finding lists without requiring a workflow-run link', () => { const noRunLinks = { pullRequestUrl: links.pullRequestUrl, commitUrl: links.commitUrl }; - expect(renderBugbotStatusCard(projection(), 'es-ES', noRunLinks)).toContain( - 'No hay hallazgos registrados', - ); + expect(renderBugbotStatusCard(projection(), 'es-ES', noRunLinks)).toContain('revisión completada'); const long = buildBugbotReviewProjection({ pullRequestNumber: 358, analyzedHeadSha: head, @@ -145,10 +147,8 @@ describe('Bugbot review presentation', () => { })), }); const body = renderBugbotStatusCard(long, 'en-US', noRunLinks); - expect(body).toContain('…and 2 more'); expect(body).not.toContain('Workflow run'); - expect(body).toContain('[x] obsolete'); - expect(body).toContain('[x] dismissed'); + expect(body).not.toContain('### Findings'); }); it('rejects malformed and outdated status markers', () => { diff --git a/src/application/policies/__tests__/publication_identity_policy.test.ts b/src/application/policies/__tests__/publication_identity_policy.test.ts new file mode 100644 index 000000000..748b26c6c --- /dev/null +++ b/src/application/policies/__tests__/publication_identity_policy.test.ts @@ -0,0 +1,79 @@ +import { + buildDuplicateMarker, + buildPublicationMarker, + buildPublicationReplyMarker, + createSemanticDigest, + parsePublicationMarker, + parsePublicationReplyMarker, +} from '../publication_identity_policy'; + +const marker = { + identity: { topic: 'progress' as const, target: { kind: 'issue' as const, number: 42 }, key: 'work' }, + sourceVersion: 'head:abc1234', + digest: '0123abcd', +}; + +describe('publication identity policy', () => { + it('round-trips a strict shared marker', () => { + const rendered = buildPublicationMarker(marker); + + expect(rendered).toBe(''); + expect(parsePublicationMarker(`prefix\n${rendered}\nsuffix`)).toEqual(marker); + }); + + it('round-trips pull-request targets', () => { + const value = { ...marker, identity: { ...marker.identity, target: { kind: 'pull-request' as const, number: 9 } } }; + expect(parsePublicationMarker(buildPublicationMarker(value))?.identity.target).toEqual({ kind: 'pull-request', number: 9 }); + }); + + it.each([ + undefined, + null, + '', + '', + '', + '', + '', + ])('treats malformed or unknown markers as inert: %p', (body) => { + expect(parsePublicationMarker(body)).toBeUndefined(); + }); + + it.each([ + { ...marker, identity: { ...marker.identity, key: 'unsafe key' } }, + { ...marker, sourceVersion: 'unsafe/source' }, + { ...marker, digest: 'not-hex' }, + ])('rejects unsafe marker construction', (value) => { + expect(() => buildPublicationMarker(value)).toThrow(); + }); + + it('creates stable order-independent semantic digests', () => { + expect(createSemanticDigest({ b: 2, a: [1, 'x'] })).toBe(createSemanticDigest({ a: [1, 'x'], b: 2 })); + expect(createSemanticDigest({ a: 1 })).not.toBe(createSemanticDigest({ a: 2 })); + expect(createSemanticDigest(undefined)).toHaveLength(16); + }); + + it('builds only valid duplicate tombstones', () => { + expect(buildDuplicateMarker(12)).toContain('canonical="12"'); + expect(() => buildDuplicateMarker(0)).toThrow('positive integer'); + expect(() => buildDuplicateMarker(1.5)).toThrow('positive integer'); + }); + + it('round-trips strict reply correlation metadata', () => { + const reply = { target: 'issue:42', correlationId: 'comment:99', messageKey: 'copilot-help', digest: '0123abcd' }; + expect(parsePublicationReplyMarker(buildPublicationReplyMarker(reply))).toEqual(reply); + }); + + it.each([ + '', + '', + '', + ])('treats malformed reply markers as inert', (body) => { + expect(parsePublicationReplyMarker(body)).toBeUndefined(); + }); + + it('rejects unsafe reply marker construction', () => { + expect(() => buildPublicationReplyMarker({ + target: 'issue:42', correlationId: 'unsafe value', messageKey: 'copilot-help', digest: '0123abcd', + })).toThrow('unsafe identity'); + }); +}); diff --git a/src/application/policies/__tests__/publication_message_catalog.test.ts b/src/application/policies/__tests__/publication_message_catalog.test.ts new file mode 100644 index 000000000..51c489538 --- /dev/null +++ b/src/application/policies/__tests__/publication_message_catalog.test.ts @@ -0,0 +1,26 @@ +import { + ENGLISH_PUBLICATION_CATALOG, + SPANISH_PUBLICATION_CATALOG, + resolveStaticPublicationCatalog, +} from '../publication_message_catalog'; + +describe('publication message catalog', () => { + it.each([ + ['en-US', 'en-US', false], + ['en-GB', 'en-US', false], + ['es-ES', 'es-ES', false], + ['es-MX', 'es-ES', false], + ['fr-FR', 'en-US', true], + ] as const)('resolves %s through deterministic catalogs', (locale, catalogLocale, fallback) => { + const resolved = resolveStaticPublicationCatalog(locale); + expect(resolved.catalog.locale).toBe(catalogLocale); + expect(resolved.fallback).toBe(fallback); + }); + + it('keeps English and Spanish catalogs structurally identical and immutable', () => { + expect(Object.keys(ENGLISH_PUBLICATION_CATALOG)).toEqual(Object.keys(SPANISH_PUBLICATION_CATALOG)); + expect(Object.keys(ENGLISH_PUBLICATION_CATALOG.progressState)).toEqual(Object.keys(SPANISH_PUBLICATION_CATALOG.progressState)); + expect(Object.isFrozen(ENGLISH_PUBLICATION_CATALOG)).toBe(true); + expect(Object.isFrozen(SPANISH_PUBLICATION_CATALOG.progressState)).toBe(true); + }); +}); diff --git a/src/application/policies/__tests__/result_publication_policy.test.ts b/src/application/policies/__tests__/result_publication_policy.test.ts deleted file mode 100644 index 48c7de7f6..000000000 --- a/src/application/policies/__tests__/result_publication_policy.test.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { Result } from '../../../data/model/result'; -import type { Images } from '../../../data/model/images'; -import { - buildDebugLogSection, - hasPublishableContent, - renderResultSections, - resolveResultPublicationIssueNumber, - resolveResultPublicationPresentation, -} from '../result_publication_policy'; -import { ApplicationError } from '../../errors/application_error'; -import { buildApplicationErrorPresentation } from '../application_error_presentation_policy'; -import type { ResultPublicationRecord } from '../result_publication_contracts'; - -const images = { - issueAutomaticActions: ['issue-auto'], - issueReleaseGifs: ['issue-release'], - issueHotfixGifs: [], - issueBugfixGifs: [], - issueFeatureGifs: [], - issueDocsGifs: [], - issueChoreGifs: [], - pullRequestAutomaticActions: ['pr-auto'], - pullRequestReleaseGifs: [], - pullRequestHotfixGifs: [], - pullRequestBugfixGifs: [], - pullRequestFeatureGifs: [], - pullRequestDocsGifs: [], - pullRequestChoreGifs: [], -} as unknown as Images; - -function publicationRecord(result: Result): ResultPublicationRecord { - return { - id: result.id, - executed: result.executed, - steps: result.steps, - reminders: result.reminders, - errors: result.errors.map(buildApplicationErrorPresentation), - stepFormat: result.stepFormat, - }; -} - -describe('result publication policy', () => { - it('resolves publication targets in lifecycle precedence order', () => { - expect(resolveResultPublicationIssueNumber({ - isSingleAction: true, - singleActionIssue: 11, - isIssue: true, - issueNumber: 22, - isPullRequest: true, - pullRequestNumber: 33, - isPush: true, - pushIssueNumber: 44, - })).toBe(11); - expect(resolveResultPublicationIssueNumber({ - isSingleAction: false, - singleActionIssue: 11, - isIssue: false, - issueNumber: 22, - isPullRequest: true, - pullRequestNumber: 33, - isPush: false, - pushIssueNumber: 44, - })).toBe(33); - expect(resolveResultPublicationIssueNumber({ - isSingleAction: false, - singleActionIssue: 11, - isIssue: false, - issueNumber: 0, - isPullRequest: false, - pullRequestNumber: 33, - isPush: true, - pushIssueNumber: 0, - })).toBeUndefined(); - expect(resolveResultPublicationIssueNumber({ - isSingleAction: false, - singleActionIssue: 11, - isIssue: false, - issueNumber: 0, - isPullRequest: false, - pullRequestNumber: 0, - isPush: true, - pushIssueNumber: 44, - })).toBe(44); - }); - it('chooses the most specific issue presentation before the default', () => { - expect(resolveResultPublicationPresentation({ - isIssue: true, - isPullRequest: false, - issueNotBranched: false, - releaseActive: true, - hotfixActive: false, - isBugfix: false, - isFeature: false, - isDocs: false, - isChore: false, - images, - }, (values) => values[0])).toEqual({ title: '🚀 Release Actions', image: 'issue-release' }); - }); - - it('uses the pull request automatic presentation when no type is active', () => { - expect(resolveResultPublicationPresentation({ - isIssue: false, - isPullRequest: true, - issueNotBranched: false, - releaseActive: false, - hotfixActive: false, - isBugfix: false, - isFeature: false, - isDocs: false, - isChore: false, - images, - }, (values) => values[0])).toEqual({ title: '🪄 Automatic Actions', image: 'pr-auto' }); - }); - - it('renders plain and markdown steps without renumbering markdown', () => { - const sections = renderResultSections([ - new Result({ id: 'plain', steps: ['first', ' '], reminders: ['remember'] }), - new Result({ id: 'markdown', stepFormat: 'markdown', steps: ['## Heading\n\n1. second'] }), - new Result({ id: 'error', errors: [new ApplicationError('workflow.failed', 'failure')] }), - ].map(publicationRecord)); - - expect(sections.content).toBe('1. first\n\n## Heading\n\n1. second\n'); - expect(sections.footer).toContain('1. remember'); - expect(sections.errors).toContain('**Impact:** The workflow could not complete the requested operation.'); - expect(sections.errors).toContain('**Cause (`workflow.failed`):** failure'); - expect(sections.errors).toContain('**Action:** Inspect the current state and retry the failed step.'); - expect(sections.errors).toContain('**Retained state:** Existing persisted state'); - expect(sections.errors).toMatch(/\*\*Reference:\*\* `[0-9a-f-]{36}`/); - }); - - it('neutralizes agent-controlled GitHub semantics before publication', () => { - const sections = renderResultSections([ - new Result({ - id: 'untrusted', - stepFormat: 'markdown', - steps: ['\n@octocat\n/fix --force'], - reminders: ['@maintainer /close'], - errors: [new ApplicationError('workflow.failed', 'token=should-not-become-a-control')], - }), - ].map(publicationRecord)); - - expect(sections.content).toContain('<!-- hidden -->'); - expect(sections.content).toContain('@\u200b octocat'.replace(' ', '')); - expect(sections.content).toContain('\u200b/fix'.replace('\\u200b', '\u200b')); - expect(sections.footer).toContain('@\u200bmaintainer'.replace('\\u200b', '\u200b')); - }); - - it('redacts credential-like values from infrastructure errors before publication', () => { - const sections = renderResultSections([ - new Result({ errors: [new ApplicationError('provider.unavailable', 'GitHub rejected token=gho_secret-value')] }), - ].map(publicationRecord)); - - expect(sections.errors).toContain('token=[redacted]'); - expect(sections.errors).not.toContain('gho_secret-value'); - }); - - it('omits empty reminders but still presents semantic recovery details for an empty message', () => { - const sections = renderResultSections([ - new Result({ reminders: [' '], errors: [new ApplicationError('workflow.failed', '\n')] }), - ].map(publicationRecord)); - - expect(sections.footer).toBe(''); - expect(sections.errors).toContain('**Cause (`workflow.failed`):**'); - expect(sections.errors).toContain('**Action:**'); - }); - - it('builds debug content only when enabled and populated', () => { - expect(buildDebugLogSection(false, 'log')).toBe(''); - expect(buildDebugLogSection(true, '')).toBe(''); - expect(buildDebugLogSection(true, 'log')).toContain('Debug log'); - }); - - it('keeps injected workflow commands and nested markdown fences inert in debug output', () => { - const section = buildDebugLogSection(true, '::error::bad\n@attacker\n```\n/fix'); - - expect(section).not.toContain('::error::bad'); - expect(section).not.toContain('```\n/fix'); - expect(section).toContain('@\u200battacker'); - expect(section).toContain('\u200b/fix'); - }); - - it('detects publishable sections independently of the title', () => { - expect(hasPublishableContent({ content: '', footer: '', errors: '' }, '')).toBe(false); - expect(hasPublishableContent({ content: '', footer: '', errors: '' }, 'debug')).toBe(false); - expect(hasPublishableContent({ content: '', footer: 'Reminder', errors: '' }, '')).toBe(false); - expect(hasPublishableContent({ content: 'recommendation', footer: '', errors: '' }, 'debug')).toBe(true); - }); -}); diff --git a/src/application/policies/__tests__/semantic_result_publication_policy.test.ts b/src/application/policies/__tests__/semantic_result_publication_policy.test.ts new file mode 100644 index 000000000..13fb8578b --- /dev/null +++ b/src/application/policies/__tests__/semantic_result_publication_policy.test.ts @@ -0,0 +1,128 @@ +import { Result } from '../../../data/model/result'; +import { + renderSemanticReply, + renderSemanticStatus, + selectSemanticReplyIntents, + selectSemanticStatusIntents, +} from '../semantic_result_publication_policy'; + +describe('semantic result publication policy', () => { + it('selects only successful, executed, validated plan and progress payloads', () => { + const results = [ + new Result({ id: 'RecommendStepsUseCase', success: true, executed: true, payload: { issueNumber: 7, recommendedSteps: '1. Build', recommendationState: { issueDescriptionFingerprint: 'abc' } } }), + new Result({ id: 'CheckProgressUseCase', success: true, executed: true, payload: { issueNumber: 7, progress: 101.2, summary: ' Done ', remaining: '', branch: '', developmentBranch: ' develop ' } }), + new Result({ id: 'RecommendStepsUseCase', success: false, executed: true, payload: { issueNumber: 7, recommendedSteps: 'ignored' } }), + new Result({ id: 'CheckProgressUseCase', success: true, executed: false, payload: { issueNumber: 7, progress: 20, summary: 'ignored' } }), + new Result({ id: 'Other', success: true, executed: true, payload: { issueNumber: 7, progress: 20, summary: 'ignored' } }), + ]; + + const intents = selectSemanticStatusIntents({ locale: 'en-US', results }); + + expect(intents).toHaveLength(2); + expect(intents[0]).toMatchObject({ kind: 'status', identity: { topic: 'plan', key: 'implementation' }, projection: { kind: 'plan' } }); + expect(intents[1]).toMatchObject({ identity: { topic: 'progress', key: 'work' }, projection: { progress: 100, summary: 'Done', developmentBranch: 'develop' } }); + expect(intents[1].projection).not.toHaveProperty('remaining'); + expect(intents[1].projection).not.toHaveProperty('branch'); + }); + + it.each([ + new Result({ id: 'RecommendStepsUseCase', success: true, executed: true, payload: null }), + new Result({ id: 'RecommendStepsUseCase', success: true, executed: true, payload: { issueNumber: 0, recommendedSteps: 'x' } }), + new Result({ id: 'RecommendStepsUseCase', success: true, executed: true, payload: { issueNumber: 1, recommendedSteps: ' ' } }), + new Result({ id: 'CheckProgressUseCase', success: true, executed: true, payload: { issueNumber: 1, progress: '10', summary: 'x' } }), + new Result({ id: 'CheckProgressUseCase', success: true, executed: true, payload: { issueNumber: 1, progress: 10, summary: 1 } }), + ])('rejects malformed compatibility payloads', (result) => { + expect(selectSemanticStatusIntents({ locale: 'en-US', results: [result] })).toEqual([]); + }); + + it('bounds and sanitizes plan and progress presentation', () => { + const [plan] = selectSemanticStatusIntents({ + locale: 'en-US', + results: [new Result({ id: 'RecommendStepsUseCase', success: true, executed: true, payload: { issueNumber: 2, recommendedSteps: `# Unsafe\n@attacker\n/fix\n${'x'.repeat(10_000)}` } })], + }); + const [progress] = selectSemanticStatusIntents({ + locale: 'en-US', + results: [new Result({ id: 'CheckProgressUseCase', success: true, executed: true, payload: { issueNumber: 2, progress: -4, summary: '@attacker', remaining: '/fix' } })], + }); + + const planBody = renderSemanticStatus(plan); + const progressBody = renderSemanticStatus(progress); + expect(planBody.length).toBeLessThan(9_000); + expect(planBody).toContain('@\u200battacker'); + expect(planBody).toContain('\u200b/fix'); + expect(progressBody).toContain('## Progress: 0% — not started'); + expect(progressBody).toContain('**Next:** \u200b/fix'); + }); + + it('omits the next section for incomplete progress without remaining work', () => { + const [intent] = selectSemanticStatusIntents({ + locale: 'en-US', + results: [new Result({ id: 'CheckProgressUseCase', success: true, executed: true, payload: { issueNumber: 2, progress: 50, summary: '' } })], + }); + const body = renderSemanticStatus(intent); + expect(body).toContain('Progress was assessed without a summary.'); + expect(body).not.toContain('**Next:**'); + }); + + it.each([ + ['help', '## Copilot commands'], + ['welcome', 'Hi! I’m **@vypbot**'], + ] as const)('selects and renders an idempotent %s reply without reading steps', (kind, expected) => { + const [intent] = selectSemanticReplyIntents({ + locale: 'en-US', + target: { kind: 'issue', number: 7 }, + correlationId: 'comment:12', + botLogin: 'vypbot', + results: [new Result({ + id: `Comment.${kind}`, success: true, executed: true, + steps: ['legacy text must not be selected'], + payload: { publication: { kind, botLogin: 'vypbot' } }, + })], + }); + + expect(intent).toMatchObject({ kind: 'reply', correlationId: 'comment:12', messageKey: `copilot-${kind}` }); + expect(renderSemanticReply(intent)).toContain(expected); + expect(renderSemanticReply(intent)).not.toContain('legacy text'); + }); + + it('renders a typed status-command projection in the configured locale', () => { + const [intent] = selectSemanticReplyIntents({ + locale: 'es-ES', target: { kind: 'pull-request', number: 9 }, correlationId: 'comment:22', + results: [new Result({ + id: 'Comment.Status', success: true, executed: true, + payload: { status: { + owner: 'acme', repository: 'widgets', event: 'issue_comment', action: 'created', target: 'pull-request', + pullRequestNumber: 9, branch: 'feature/x', lifecycle: 'reviewing', issueLabels: [], pullRequestLabels: ['reviewing'], + pullRequestDescriptionMode: 'replace', + } }, + })], + }); + + expect(renderSemanticReply(intent)).toContain('## Estado de Copilot'); + expect(renderSemanticReply(intent)).toContain('target="pr:9"'); + }); + + it('renders a concise localized access-policy explanation', () => { + const [intent] = selectSemanticReplyIntents({ + locale: 'es-ES', target: { kind: 'issue', number: 8 }, correlationId: 'event:abc12345', + results: [new Result({ + id: 'CloseNotAllowedIssueUseCase', success: true, executed: true, + payload: { publication: { kind: 'access-policy' } }, + })], + }); + const body = renderSemanticReply(intent); + expect(body).toContain('Issue cerrada: se requiere acceso de colaborador'); + expect(body).toContain('contacta con un mantenedor'); + expect(body).not.toContain('banned'); + }); + + it('omits malformed, failed, uncorrelated, or untargeted replies', () => { + const result = new Result({ id: 'Comment.Help', success: true, executed: true, payload: { publication: { kind: 'help' } } }); + expect(selectSemanticReplyIntents({ locale: 'en-US', results: [result] })).toEqual([]); + expect(selectSemanticReplyIntents({ locale: 'en-US', target: { kind: 'issue', number: 1 }, correlationId: '', results: [result] })).toEqual([]); + expect(selectSemanticReplyIntents({ + locale: 'en-US', target: { kind: 'issue', number: 1 }, correlationId: 'comment:1', + results: [new Result({ id: 'Comment.Help', success: false, executed: true, payload: { publication: { kind: 'help' } } })], + })).toEqual([]); + }); +}); diff --git a/src/application/policies/branch_sync_notification_policy.ts b/src/application/policies/branch_sync_notification_policy.ts index 66912ac95..9530620df 100644 --- a/src/application/policies/branch_sync_notification_policy.ts +++ b/src/application/policies/branch_sync_notification_policy.ts @@ -2,12 +2,14 @@ import type { BranchDependency, BranchSyncComparison, BranchSyncNotificationComment, -} from "../ports/branch_sync_ports"; -import { githubUsersMatch } from "../../domain/github_user_policy"; +} from '../ports/branch_sync_ports'; +import { githubUsersMatch } from '../../domain/github_user_policy'; +import { baseLanguage } from '../../domain/locale'; +import { buildPublicationMarker, createSemanticDigest } from './publication_identity_policy'; -export const BRANCH_SYNC_STALE_MARKER = ""; -export const BRANCH_SYNC_ALIGNED_MARKER = ""; -const BRANCH_SYNC_KEY_MARKER = "'; +export const BRANCH_SYNC_ALIGNED_MARKER = ''; +const BRANCH_SYNC_KEY_MARKER = ''; @@ -14,8 +15,38 @@ export function normalizeCopilotBotUsername(username: string | undefined): strin } /** Renders the stable command reference used by /copilot help. */ -export function buildCopilotHelpMessage(username?: string): string { +export function buildCopilotHelpMessage(username?: string, locale = 'en-US'): string { const bot = normalizeCopilotBotUsername(username); + if (baseLanguage(locale) === 'es') return `## Comandos de Copilot + +Soy **@${bot}**, el asistente del repositorio. Usa estos comandos en una issue o pull request: + +### Solo lectura + +- \`/copilot help\` — muestra esta referencia. +- \`/copilot plan\` — propone un plan de implementación. +- \`/copilot clarify\` — identifica información pendiente y supuestos. +- \`/copilot estimate\` — estima el alcance y la complejidad. +- \`/copilot test-plan\` — propone una estrategia de pruebas. +- \`/copilot explain \` — explica código o comportamiento. +- \`/copilot diagnose\` — investiga un problema y sus posibles causas. +- \`/copilot analyze\` — analiza la issue, rama o pull request actual. +- \`/copilot review [effort=smart|low|default|high] [dry-run=true] [trace-rules=true] [suggested-changes=false]\` — ejecuta Bugbot con ajustes opcionales. +- \`/copilot findings\` — muestra los hallazgos potenciales. +- \`/copilot recheck\` — repite la revisión y reconcilia los hallazgos. +- \`/copilot description\` — actualiza la descripción del pull request. +- \`/copilot status\` — muestra el estado actual de la automatización. + +### Cambios + +- \`/copilot fix \` — corrige un hallazgo. +- \`/copilot fix all\` — corrige todos los hallazgos sin resolver. +- \`/copilot dismiss \` — descarta un hallazgo. +- \`/copilot remember \` — añade una regla de revisión autorizada y versionada. +- \`/copilot implement \` — aplica un cambio solicitado explícitamente. +- \`/copilot sync-branch [--dry-run] [--no-agent] [--from ]\` — integra la rama padre de forma segura. + +También puedes mencionar a **@${bot}** y escribir una pregunta. Los comandos que modifican archivos están limitados a mantenedores autorizados, ejecutan las comprobaciones configuradas e informan de los cambios resultantes.`; return `## Copilot commands I’m **@${bot}**, the repository assistant. Use these commands on an issue or pull request: @@ -49,8 +80,15 @@ You can also ask a question in natural language by mentioning **@${bot}**. For e } /** Renders the one-time onboarding comment for a newly created issue. */ -export function buildCopilotWelcomeMessage(username?: string): string { +export function buildCopilotWelcomeMessage(username?: string, locale = 'en-US'): string { const bot = normalizeCopilotBotUsername(username); + if (baseLanguage(locale) === 'es') return `${COPILOT_WELCOME_MARKER} + +Hola, soy **@${bot}**, el asistente de Copilot de este repositorio. + +Puedo responder preguntas, explicar el código, proponer planes de implementación y pruebas, revisar issues y pull requests y ayudar a los mantenedores autorizados a aplicar cambios. + +Usa \`/copilot help\` para ver los comandos disponibles o menciona a **@${bot}** con tu pregunta.`; return `${COPILOT_WELCOME_MARKER} Hi! I’m **@${bot}**, the Copilot assistant for this repository. @@ -68,5 +106,6 @@ export function buildCopilotWelcomeResult(username?: string): Result { executed: true, stepFormat: 'markdown', steps: [buildCopilotWelcomeMessage(username)], + payload: Object.freeze({ publication: Object.freeze({ kind: 'welcome', botLogin: normalizeCopilotBotUsername(username) }) }), }); } diff --git a/src/application/policies/deployment_presentation_policy.ts b/src/application/policies/deployment_presentation_policy.ts index 7e7dc5de5..742c7f250 100644 --- a/src/application/policies/deployment_presentation_policy.ts +++ b/src/application/policies/deployment_presentation_policy.ts @@ -1,5 +1,6 @@ import type { DeploymentOperationSnapshot, DeploymentPhase, ReconciliationTargetState } from "../../domain/deployment_operation"; import { buildManagedPullRequestMarker } from "../../domain/managed_pull_request"; +import { buildPublicationMarker, createSemanticDigest } from './publication_identity_policy'; type SupportedLocale = "en-US" | "es-ES"; @@ -147,6 +148,15 @@ export function renderDeploymentDashboard(operation: DeploymentOperationSnapshot const title = operation.kind === "release" ? messages.release : messages.hotfix; const action = deploymentAction(operation, messages); const lines = [ + buildPublicationMarker({ + identity: { + topic: 'release', + target: { kind: 'issue', number: context.issue }, + key: `operation:${createSemanticDigest(operation.operationId)}`, + }, + sourceVersion: `revision:${operation.revision}`, + digest: createSemanticDigest({ locale: context.issueLocale, operation }), + }), deploymentDashboardMarker(operation.operationId, context.issue), "", `# ${operation.phase === "blocked" ? "❌" : operation.phase === "completed" ? "✅" : "🚀"} ${title} ${inline(operation.version)}`, "", `> **${messages.currentStatus}: ${messages.phase[operation.phase]}.**`, diff --git a/src/application/policies/publication_identity_policy.ts b/src/application/policies/publication_identity_policy.ts new file mode 100644 index 000000000..852021d14 --- /dev/null +++ b/src/application/policies/publication_identity_policy.ts @@ -0,0 +1,90 @@ +import { createHash } from 'node:crypto'; +import { + PUBLICATION_TOPICS, + publicationTargetToken, + type PublicationIdentity, + type PublicationTopic, +} from '../../domain/github_publication'; + +export const PUBLICATION_SCHEMA = '1'; +export const PUBLICATION_MARKER_PREFIX = 'copilot:publication'; +export const PUBLICATION_DUPLICATE_MARKER_PREFIX = 'copilot:publication-duplicate'; +export const PUBLICATION_REPLY_MARKER_PREFIX = 'copilot:reply'; +const SAFE_VALUE = /^[A-Za-z0-9._:-]{1,128}$/u; +const DIGEST = /^[a-f0-9]{8,64}$/u; + +export interface PublicationMarker { + readonly identity: PublicationIdentity; + readonly sourceVersion: string; + readonly digest: string; +} + +export interface PublicationReplyMarker { + readonly target: string; + readonly correlationId: string; + readonly messageKey: string; + readonly digest: string; +} + +export function createSemanticDigest(value: unknown): string { + return createHash('sha256').update(stableSerialize(value), 'utf8').digest('hex').slice(0, 16); +} + +export function buildPublicationMarker(marker: PublicationMarker): string { + const target = publicationTargetToken(marker.identity.target); + for (const value of [marker.identity.topic, target, marker.identity.key, marker.sourceVersion]) { + if (!SAFE_VALUE.test(value)) throw new Error('Publication marker contains an unsafe identity value.'); + } + if (!DIGEST.test(marker.digest)) throw new Error('Publication marker contains an invalid digest.'); + return ``; +} + +export function parsePublicationMarker(body: string | null | undefined): PublicationMarker | undefined { + if (typeof body !== 'string') return undefined; + const match = body.match(//u); + if (!match) return undefined; + const topic = match[1] as PublicationTopic; + if (!PUBLICATION_TOPICS.includes(topic)) return undefined; + const number = Number(match[3]); + if (!Number.isSafeInteger(number) || number < 1) return undefined; + return Object.freeze({ + identity: Object.freeze({ + topic, + target: Object.freeze({ kind: match[2] === 'pr' ? 'pull-request' : 'issue', number }), + key: match[4], + }), + sourceVersion: match[5], + digest: match[6], + }); +} + +export function buildPublicationReplyMarker(marker: PublicationReplyMarker): string { + for (const value of [marker.target, marker.correlationId, marker.messageKey]) { + if (!SAFE_VALUE.test(value)) throw new Error('Publication reply marker contains an unsafe identity value.'); + } + if (!DIGEST.test(marker.digest)) throw new Error('Publication reply marker contains an invalid digest.'); + return ``; +} + +export function parsePublicationReplyMarker(body: string | null | undefined): PublicationReplyMarker | undefined { + if (typeof body !== 'string') return undefined; + const match = body.match(//u); + if (!match) return undefined; + return Object.freeze({ target: match[1], correlationId: match[2], messageKey: match[3], digest: match[4] }); +} + +export function buildDuplicateMarker(canonicalCommentId: number): string { + if (!Number.isSafeInteger(canonicalCommentId) || canonicalCommentId < 1) { + throw new Error('Canonical comment id must be a positive integer.'); + } + return ``; +} + +function stableSerialize(value: unknown): string { + if (Array.isArray(value)) return `[${value.map(stableSerialize).join(',')}]`; + if (value && typeof value === 'object') { + const record = value as Record; + return `{${Object.keys(record).sort().map(key => `${JSON.stringify(key)}:${stableSerialize(record[key])}`).join(',')}}`; + } + return JSON.stringify(value) ?? 'null'; +} diff --git a/src/application/policies/publication_message_catalog.ts b/src/application/policies/publication_message_catalog.ts new file mode 100644 index 000000000..cb83bfea0 --- /dev/null +++ b/src/application/policies/publication_message_catalog.ts @@ -0,0 +1,72 @@ +import { baseLanguage, canonicalizeLocaleTag, DEFAULT_REPOSITORY_LOCALE } from '../../domain/locale'; + +export interface PublicationMessageCatalog { + readonly locale: string; + readonly implementationPlan: string; + readonly planReady: string; + readonly planAcceptance: string; + readonly commandsHint: string; + readonly progress: string; + readonly progressState: Readonly>; + readonly currentStatus: string; + readonly next: string; + readonly noActionRequired: string; + readonly supersededStatus: string; + readonly viewCurrentStatus: string; + readonly duplicateReply: string; + readonly viewOriginalResponse: string; +} + +const ENGLISH: PublicationMessageCatalog = Object.freeze({ + locale: 'en-US', + implementationPlan: 'Implementation plan', + planReady: 'Ready to start. No action is required from maintainers before implementation.', + planAcceptance: 'Acceptance', + commandsHint: 'Need something else? Mention the bot with a question or use `/copilot help`.', + progress: 'Progress', + progressState: Object.freeze({ 'not-started': 'not started', 'in-progress': 'in progress', complete: 'complete' }), + currentStatus: 'Current status', + next: 'Next', + noActionRequired: 'No action required.', + supersededStatus: 'This status was superseded by the canonical card.', + viewCurrentStatus: 'View current status', + duplicateReply: 'This duplicate response was suppressed.', + viewOriginalResponse: 'View the original response', +}); + +const SPANISH: PublicationMessageCatalog = Object.freeze({ + locale: 'es-ES', + implementationPlan: 'Plan de implementación', + planReady: 'Listo para comenzar. No se requiere ninguna acción de mantenimiento antes de la implementación.', + planAcceptance: 'Aceptación', + commandsHint: '¿Necesitas algo más? Menciona al bot con una pregunta o usa `/copilot help`.', + progress: 'Progreso', + progressState: Object.freeze({ 'not-started': 'sin iniciar', 'in-progress': 'en curso', complete: 'completado' }), + currentStatus: 'Estado actual', + next: 'Siguiente paso', + noActionRequired: 'No se requiere ninguna acción.', + supersededStatus: 'Este estado fue sustituido por la tarjeta canónica.', + viewCurrentStatus: 'Ver estado actual', + duplicateReply: 'Esta respuesta duplicada se ha omitido.', + viewOriginalResponse: 'Ver la respuesta original', +}); + +export interface ResolvedPublicationCatalog { + readonly requestedLocale: string; + readonly catalog: PublicationMessageCatalog; + readonly fallback: boolean; +} + +export function resolveStaticPublicationCatalog(locale: string): ResolvedPublicationCatalog { + const requestedLocale = canonicalizeLocaleTag(locale || DEFAULT_REPOSITORY_LOCALE); + if (baseLanguage(requestedLocale) === 'es') { + return Object.freeze({ requestedLocale, catalog: SPANISH, fallback: false }); + } + if (baseLanguage(requestedLocale) === 'en') { + return Object.freeze({ requestedLocale, catalog: ENGLISH, fallback: false }); + } + return Object.freeze({ requestedLocale, catalog: ENGLISH, fallback: true }); +} + +export const ENGLISH_PUBLICATION_CATALOG = ENGLISH; +export const SPANISH_PUBLICATION_CATALOG = SPANISH; diff --git a/src/application/policies/result_publication_contracts.ts b/src/application/policies/result_publication_contracts.ts deleted file mode 100644 index 019bc9f2e..000000000 --- a/src/application/policies/result_publication_contracts.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { ApplicationErrorPresentation } from './application_error_presentation_policy'; - -export interface ResultPublicationImages { - readonly imagesOnIssue: boolean; - readonly issueAutomaticActions: readonly string[]; - readonly issueFeatureGifs: readonly string[]; - readonly issueBugfixGifs: readonly string[]; - readonly issueReleaseGifs: readonly string[]; - readonly issueHotfixGifs: readonly string[]; - readonly issueDocsGifs: readonly string[]; - readonly issueChoreGifs: readonly string[]; - readonly imagesOnPullRequest: boolean; - readonly pullRequestAutomaticActions: readonly string[]; - readonly pullRequestFeatureGifs: readonly string[]; - readonly pullRequestBugfixGifs: readonly string[]; - readonly pullRequestReleaseGifs: readonly string[]; - readonly pullRequestHotfixGifs: readonly string[]; - readonly pullRequestDocsGifs: readonly string[]; - readonly pullRequestChoreGifs: readonly string[]; -} - -export interface ResultPublicationContext { - readonly isIssue: boolean; - readonly isPullRequest: boolean; - readonly issueNotBranched: boolean; - readonly releaseActive: boolean; - readonly hotfixActive: boolean; - readonly isBugfix: boolean; - readonly isFeature: boolean; - readonly isDocs: boolean; - readonly isChore: boolean; - readonly images: ResultPublicationImages; -} - -export interface ResultPublicationPresentation { - readonly title: string; - readonly image?: string; -} - -export interface ResultPublicationSections { - readonly content: string; - readonly footer: string; - readonly errors: string; -} - -export interface ResultPublicationTargetInput { - readonly isSingleAction: boolean; - readonly singleActionIssue: number; - readonly isIssue: boolean; - readonly issueNumber: number; - readonly isPullRequest: boolean; - readonly pullRequestNumber: number; - readonly isPush: boolean; - readonly pushIssueNumber: number; -} - -export interface ResultPublicationRecord { - readonly id: string; - readonly executed: boolean; - readonly steps: readonly string[]; - readonly reminders: readonly string[]; - readonly errors: readonly ApplicationErrorPresentation[]; - readonly stepFormat: 'plain' | 'markdown'; -} diff --git a/src/application/policies/result_publication_policy.ts b/src/application/policies/result_publication_policy.ts deleted file mode 100644 index 73024d615..000000000 --- a/src/application/policies/result_publication_policy.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { sanitizeAgentMarkdown } from './github_comment_publication_policy'; -import { selectResultPublicationPresentation } from './result_publication_presentation_policy'; -import { renderResultSections as renderPublicationSections } from './result_publication_sections_policy'; -import type { - ResultPublicationContext, - ResultPublicationPresentation, - ResultPublicationRecord, - ResultPublicationSections, - ResultPublicationTargetInput, -} from './result_publication_contracts'; - -export type { - ResultPublicationContext, - ResultPublicationPresentation, - ResultPublicationSections, - ResultPublicationTargetInput, -} from './result_publication_contracts'; - -type ImageSelector = (images: readonly string[]) => string | undefined; -const MAX_DEBUG_LOG_LENGTH = 12_000; - -/** Resolves the GitHub discussion that receives a result comment. */ -export function resolveResultPublicationIssueNumber(input: ResultPublicationTargetInput): number | undefined { - if (input.isSingleAction) return input.singleActionIssue > 0 ? input.singleActionIssue : undefined; - if (input.isIssue) return input.issueNumber; - if (input.isPullRequest) return input.pullRequestNumber; - if (input.isPush && input.pushIssueNumber > 0) return input.pushIssueNumber; - return undefined; -} - -export function resolveResultPublicationPresentation( - context: ResultPublicationContext, - selectImage: ImageSelector, -): ResultPublicationPresentation { - return selectResultPublicationPresentation(context, selectImage); -} - -export function renderResultSections(results: readonly ResultPublicationRecord[]): ResultPublicationSections { - return renderPublicationSections(results); -} - -export function buildDebugLogSection(debug: boolean, logsText: string): string { - if (!debug || logsText.length === 0) return ''; - const safeLogs = sanitizeAgentMarkdown(logsText, MAX_DEBUG_LOG_LENGTH).replace(/```/g, ''); - if (!safeLogs.trim()) return ''; - return ` - -
-Debug log - -\`\`\` -${safeLogs} -\`\`\` -
-`; -} - -export function hasPublishableContent(sections: ResultPublicationSections, debugLogSection: string): boolean { - // Debug output belongs in the job summary/logs. Publishing it as the only - // result creates a comment with just the presentation GIF and metadata, - // which can retrigger `issue_comment` workflows indefinitely. - // Reminders are supplementary guidance and must accompany primary result - // content; they are not a result by themselves. - void debugLogSection; - return sections.content.trim().length > 0 - || sections.errors.trim().length > 0; -} diff --git a/src/application/policies/result_publication_presentation_policy.ts b/src/application/policies/result_publication_presentation_policy.ts deleted file mode 100644 index 4b64c1d55..000000000 --- a/src/application/policies/result_publication_presentation_policy.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { ResultPublicationContext, ResultPublicationPresentation } from './result_publication_contracts'; - -type ImageSelector = (images: readonly string[]) => string | undefined; - -export function selectResultPublicationPresentation( - context: ResultPublicationContext, - selectImage: ImageSelector, -): ResultPublicationPresentation { - if (context.isIssue) return selectIssuePresentation(context, selectImage); - if (context.isPullRequest) return selectPullRequestPresentation(context, selectImage); - return { title: '🪄 Automatic Actions' }; -} - -function selectIssuePresentation(context: ResultPublicationContext, selectImage: ImageSelector): ResultPublicationPresentation { - if (context.issueNotBranched) return presentation('🪄 Automatic Actions', context.images.issueAutomaticActions, selectImage); - if (context.releaseActive) return presentation('🚀 Release Actions', context.images.issueReleaseGifs, selectImage); - if (context.hotfixActive) return presentation('🔥🐛 Hotfix Actions', context.images.issueHotfixGifs, selectImage); - if (context.isBugfix) return presentation('🐛 Bugfix Actions', context.images.issueBugfixGifs, selectImage); - if (context.isFeature) return presentation('✨ Feature Actions', context.images.issueFeatureGifs, selectImage); - if (context.isDocs) return presentation('📝 Documentation Actions', context.images.issueDocsGifs, selectImage); - if (context.isChore) return presentation('🔧 Chore Actions', context.images.issueChoreGifs, selectImage); - return { title: '🪄 Automatic Actions' }; -} - -function selectPullRequestPresentation(context: ResultPublicationContext, selectImage: ImageSelector): ResultPublicationPresentation { - if (context.releaseActive) return presentation('🚀 Release Actions', context.images.pullRequestReleaseGifs, selectImage); - if (context.hotfixActive) return presentation('🔥🐛 Hotfix Actions', context.images.pullRequestHotfixGifs, selectImage); - if (context.isBugfix) return presentation('🐛 Bugfix Actions', context.images.pullRequestBugfixGifs, selectImage); - if (context.isFeature) return presentation('✨ Feature Actions', context.images.pullRequestFeatureGifs, selectImage); - if (context.isDocs) return presentation('📝 Documentation Actions', context.images.pullRequestDocsGifs, selectImage); - if (context.isChore) return presentation('🔧 Chore Actions', context.images.pullRequestChoreGifs, selectImage); - return presentation('🪄 Automatic Actions', context.images.pullRequestAutomaticActions, selectImage); -} - -function presentation(title: string, images: readonly string[], selectImage: ImageSelector): ResultPublicationPresentation { - return { title, image: selectImage(images) }; -} diff --git a/src/application/policies/result_publication_sections_policy.ts b/src/application/policies/result_publication_sections_policy.ts deleted file mode 100644 index ba2169b33..000000000 --- a/src/application/policies/result_publication_sections_policy.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { hasVisibleCommentContent } from '../../domain/comment_content_policy'; -import type { ResultPublicationRecord, ResultPublicationSections } from './result_publication_contracts'; -import { sanitizeAgentMarkdown, sanitizePublishedError } from './github_comment_publication_policy'; - -export function renderResultSections(results: readonly ResultPublicationRecord[]): ResultPublicationSections { - const renderedSteps: string[] = []; - const reminders: string[] = []; - const errors: string[] = []; - let stepIndex = 0; - - for (const result of results) { - stepIndex = appendSteps(renderedSteps, result, stepIndex); - reminders.push(...result.reminders - .map(reminder => sanitizeAgentMarkdown(reminder)) - .filter(hasVisibleCommentContent)); - errors.push(...result.errors - .map(renderPublishedApplicationError) - .filter(hasVisibleCommentContent)); - } - - return { - content: renderedSteps.length > 0 ? `${renderedSteps.join('\n\n')}\n` : '', - footer: reminders.length > 0 ? `\n## Reminder\n\n${reminders.map((reminder, index) => `${index + 1}. ${reminder}`).join('\n')}\n` : '', - errors: errors.length > 0 - ? `\n## Errors Found\n\n${errors.map((error, index) => `${index + 1}. ${error}\n`).join('\n')}\nCheck your project configuration, if everything is okay consider [opening an issue](https://github.com/vypdev/copilot/issues/new/choose).\n` - : '', - }; -} - -function renderPublishedApplicationError(view: ResultPublicationRecord['errors'][number]): string { - return [ - `**Impact:** ${sanitizePublishedError(view.impact)}`, - ` **Cause (\`${view.code}\`):** ${sanitizePublishedError(view.cause)}`, - ` **Action:** ${sanitizePublishedError(view.action)}`, - ` **Retained state:** ${sanitizePublishedError(view.retainedState)}`, - ` **Reference:** \`${view.reference}\``, - ].join('\n'); -} - -function appendSteps(renderedSteps: string[], result: ResultPublicationRecord, stepIndex: number): number { - for (const step of result.steps) { - if (!step.trim()) continue; - const safeStep = sanitizeAgentMarkdown(step); - if (!safeStep.trim()) continue; - renderedSteps.push(result.stepFormat === 'markdown' ? safeStep : `${stepIndex + 1}. ${safeStep}`); - if (result.stepFormat !== 'markdown') stepIndex += 1; - } - return stepIndex; -} diff --git a/src/application/policies/semantic_result_publication_policy.ts b/src/application/policies/semantic_result_publication_policy.ts new file mode 100644 index 000000000..f34c0763a --- /dev/null +++ b/src/application/policies/semantic_result_publication_policy.ts @@ -0,0 +1,262 @@ +import { publicationTargetToken, type PublicationTarget, type ReplyPublicationIntent, type StatusPublicationIntent } from '../../domain/github_publication'; +import type { Result } from '../../data/model/result'; +import { getResultPayload } from '../../data/model/result'; +import { sanitizeAgentMarkdown } from './github_comment_publication_policy'; +import { buildPublicationMarker, buildPublicationReplyMarker, createSemanticDigest } from './publication_identity_policy'; +import { resolveStaticPublicationCatalog } from './publication_message_catalog'; +import { buildCopilotHelpMessage, buildCopilotWelcomeMessage } from './copilot_interaction_policy'; +import { formatCopilotStatus, type CopilotStatusSnapshot } from './status_command_policy'; + +export interface PlanPublicationProjection { + readonly kind: 'plan'; + readonly recommendation: string; +} + +export interface ProgressPublicationProjection { + readonly kind: 'progress'; + readonly progress: number; + readonly summary: string; + readonly remaining?: string; + readonly branch?: string; + readonly developmentBranch?: string; +} + +export type SemanticStatusProjection = PlanPublicationProjection | ProgressPublicationProjection; +export type SemanticStatusIntent = StatusPublicationIntent; + +export type SemanticReplyProjection = + | { readonly kind: 'help'; readonly botLogin: string } + | { readonly kind: 'welcome'; readonly botLogin: string } + | { readonly kind: 'access-policy' } + | { readonly kind: 'status-command'; readonly snapshot: CopilotStatusSnapshot }; +export type SemanticReplyIntent = ReplyPublicationIntent; + +export interface SemanticPublicationContext { + readonly locale: string; + readonly results: readonly Result[]; + readonly target?: PublicationTarget; + readonly correlationId?: string; + readonly botLogin?: string; +} + +export function selectSemanticStatusIntents(context: SemanticPublicationContext): readonly SemanticStatusIntent[] { + return Object.freeze(context.results.flatMap(result => { + if (!result.executed || !result.success) return []; + const payload = getResultPayload(result.payload); + if (!payload) return []; + const plan = planIntent(result.id, payload, context.locale); + if (plan) return [plan]; + const progress = progressIntent(result.id, payload, context.locale); + return progress ? [progress] : []; + })); +} + +export function selectSemanticReplyIntents(context: SemanticPublicationContext): readonly SemanticReplyIntent[] { + if (!context.target || !positiveInteger(context.target.number) || !context.correlationId?.trim()) return []; + const correlationId = safeMarkerToken(context.correlationId); + return Object.freeze(context.results.flatMap(result => { + if (!result.executed || !result.success) return []; + const payload = getResultPayload(result.payload); + if (!payload) return []; + const publication = getResultPayload(payload.publication); + const kind = publication?.kind; + if (kind === 'help' || kind === 'welcome') { + const botLogin = typeof publication?.botLogin === 'string' && publication.botLogin.trim() + ? publication.botLogin.trim() + : context.botLogin?.trim() ?? ''; + const projection = Object.freeze({ kind, botLogin }) as SemanticReplyProjection; + return [replyIntent(context, correlationId, `copilot-${kind}`, projection)]; + } + if (kind === 'access-policy') { + return [replyIntent(context, correlationId, 'access-policy', Object.freeze({ kind }))]; + } + const snapshot = getResultPayload(payload.status); + if (result.id.endsWith('.Status') && isStatusSnapshot(snapshot)) { + const projection = Object.freeze({ + kind: 'status-command' as const, + snapshot: copyStatusSnapshot(snapshot), + }); + return [replyIntent(context, correlationId, 'copilot-status', projection)]; + } + return []; + })); +} + +export function renderSemanticReply(intent: SemanticReplyIntent): string { + const marker = buildPublicationReplyMarker({ + target: publicationTargetToken(intent.target), + correlationId: intent.correlationId, + messageKey: intent.messageKey, + digest: intent.digest, + }); + const body = intent.projection.kind === 'help' + ? buildCopilotHelpMessage(intent.projection.botLogin, intent.locale) + : intent.projection.kind === 'welcome' + ? buildCopilotWelcomeMessage(intent.projection.botLogin, intent.locale) + : intent.projection.kind === 'access-policy' + ? renderAccessPolicyReply(intent.locale) + : formatCopilotStatus(intent.projection.snapshot, intent.locale); + return `${marker}\n\n${body}`; +} + +function renderAccessPolicyReply(locale: string): string { + if (resolveStaticPublicationCatalog(locale).catalog.locale === 'es-ES') { + return [ + '## Issue cerrada: se requiere acceso de colaborador', + '', + 'Este repositorio solo permite el procesamiento automatizado de issues creadas por colaboradores autorizados.', + '', + 'Si crees que se trata de un error, contacta con un mantenedor o consulta la política de contribución del repositorio.', + ].join('\n'); + } + return [ + '## Issue closed: contributor access required', + '', + 'This repository accepts automated issue processing only from eligible contributors.', + '', + 'If you believe this is incorrect, contact a maintainer or follow the repository contribution policy.', + ].join('\n'); +} + +export function renderSemanticStatus(intent: SemanticStatusIntent): string { + const messages = resolveStaticPublicationCatalog(intent.locale).catalog; + const marker = buildPublicationMarker(intent); + if (intent.projection.kind === 'plan') { + const plan = sanitizeAgentMarkdown(intent.projection.recommendation, 8_000).trim(); + return [ + marker, + '', + `## ${messages.implementationPlan}`, + '', + `> **${messages.currentStatus}:** ${messages.planReady}`, + '', + plan, + '', + `**${messages.planAcceptance}:** ${messages.noActionRequired}`, + '', + messages.commandsHint, + ].join('\n').trim(); + } + const progress = intent.projection.progress; + const state = progress <= 0 ? 'not-started' : progress >= 100 ? 'complete' : 'in-progress'; + const lines = [ + marker, + '', + `## ${messages.progress}: ${progress}% — ${messages.progressState[state]}`, + '', + `> **${messages.currentStatus}:** ${sanitizeAgentMarkdown(intent.projection.summary, 1_500).trim()}`, + ]; + if (progress < 100 && intent.projection.remaining?.trim()) { + lines.push('', `**${messages.next}:** ${sanitizeAgentMarkdown(intent.projection.remaining, 3_000).trim()}`); + } else if (progress >= 100) { + lines.push('', messages.noActionRequired); + } + return lines.join('\n').trim(); +} + +function planIntent(id: string, payload: Record, locale: string): SemanticStatusIntent | undefined { + if (id !== 'RecommendStepsUseCase' + || !positiveInteger(payload.issueNumber) + || typeof payload.recommendedSteps !== 'string' + || !payload.recommendedSteps.trim()) return undefined; + const state = getResultPayload(payload.recommendationState); + const issueFingerprint = typeof state?.issueDescriptionFingerprint === 'string' + ? state.issueDescriptionFingerprint + : createSemanticDigest(payload.recommendedSteps); + const projection = Object.freeze({ + kind: 'plan', + recommendation: payload.recommendedSteps.trim(), + }); + return statusIntent('plan', payload.issueNumber, 'implementation', `issue-body:${safeDigest(issueFingerprint)}`, locale, projection); +} + +function progressIntent(id: string, payload: Record, locale: string): SemanticStatusIntent | undefined { + if (id !== 'CheckProgressUseCase' + || !positiveInteger(payload.issueNumber) + || typeof payload.progress !== 'number' + || typeof payload.summary !== 'string') return undefined; + const progress = Math.max(0, Math.min(100, Math.round(payload.progress))); + const projection = Object.freeze({ + kind: 'progress', + progress, + summary: payload.summary.trim() || 'Progress was assessed without a summary.', + ...(typeof payload.remaining === 'string' && payload.remaining.trim() + ? { remaining: payload.remaining.trim() } + : {}), + ...(typeof payload.branch === 'string' && payload.branch.trim() ? { branch: payload.branch.trim() } : {}), + ...(typeof payload.developmentBranch === 'string' && payload.developmentBranch.trim() + ? { developmentBranch: payload.developmentBranch.trim() } + : {}), + }); + return statusIntent('progress', payload.issueNumber, 'work', `progress:${createSemanticDigest(projection)}`, locale, projection); +} + +function statusIntent( + topic: 'plan' | 'progress', + issueNumber: number, + key: string, + sourceVersion: string, + locale: string, + projection: SemanticStatusProjection, +): SemanticStatusIntent { + return Object.freeze({ + kind: 'status', + identity: Object.freeze({ topic, target: Object.freeze({ kind: 'issue', number: issueNumber }), key }), + sourceVersion, + digest: createSemanticDigest({ locale, projection }), + locale, + projection, + }); +} + +function positiveInteger(value: unknown): value is number { + return typeof value === 'number' && Number.isSafeInteger(value) && value > 0; +} + +function replyIntent( + context: SemanticPublicationContext, + correlationId: string, + messageKey: string, + projection: SemanticReplyProjection, +): SemanticReplyIntent { + return Object.freeze({ + kind: 'reply', + target: context.target!, + correlationId, + messageKey, + locale: context.locale, + digest: createSemanticDigest({ locale: context.locale, projection }), + projection, + }); +} + +function safeMarkerToken(value: string): string { + const trimmed = value.trim(); + return /^[A-Za-z0-9._:-]{1,128}$/u.test(trimmed) ? trimmed : `digest:${createSemanticDigest(trimmed)}`; +} + +function isStatusSnapshot(value: unknown): value is CopilotStatusSnapshot { + const record = getResultPayload(value); + return Boolean(record + && typeof record.owner === 'string' + && typeof record.repository === 'string' + && typeof record.event === 'string' + && typeof record.action === 'string' + && ['issue', 'pull-request', 'push', 'repository'].includes(String(record.target)) + && Array.isArray(record.issueLabels) + && Array.isArray(record.pullRequestLabels) + && typeof record.pullRequestDescriptionMode === 'string'); +} + +function copyStatusSnapshot(snapshot: CopilotStatusSnapshot): CopilotStatusSnapshot { + return Object.freeze({ + ...snapshot, + issueLabels: Object.freeze([...snapshot.issueLabels]), + pullRequestLabels: Object.freeze([...snapshot.pullRequestLabels]), + ...(snapshot.findingStates ? { findingStates: Object.freeze({ ...snapshot.findingStates }) } : {}), + }); +} + +function safeDigest(value: string): string { + return /^[a-f0-9]{8,64}$/u.test(value) ? value : createSemanticDigest(value); +} diff --git a/src/application/policies/status_command_policy.ts b/src/application/policies/status_command_policy.ts index ec92f34cb..1e024db72 100644 --- a/src/application/policies/status_command_policy.ts +++ b/src/application/policies/status_command_policy.ts @@ -1,6 +1,7 @@ import { Result } from '../../data/model/result'; import type { CopilotLifecycleLabels } from '../../domain/copilot_lifecycle'; import { projectBugbotResultFindingStates } from './bugbot_result_finding_state_projection_policy'; +import { baseLanguage } from '../../domain/locale'; export interface CopilotStatusExecutionContext { readonly owner: string; @@ -117,7 +118,8 @@ export function buildCopilotStatusResult(snapshot: CopilotStatusSnapshot, taskId }); } -export function formatCopilotStatus(snapshot: CopilotStatusSnapshot): string { +export function formatCopilotStatus(snapshot: CopilotStatusSnapshot, locale = 'en-US'): string { + if (baseLanguage(locale) === 'es') return formatSpanishCopilotStatus(snapshot); const lines = [ '## Copilot status', `- **Repository:** ${snapshot.owner}/${snapshot.repository}`, @@ -137,3 +139,24 @@ export function formatCopilotStatus(snapshot: CopilotStatusSnapshot): string { } return lines.join('\n'); } + +function formatSpanishCopilotStatus(snapshot: CopilotStatusSnapshot): string { + const lines = [ + '## Estado de Copilot', + `- **Repositorio:** ${snapshot.owner}/${snapshot.repository}`, + `- **Destino:** ${snapshot.target}${snapshot.issueNumber ? ` #${snapshot.issueNumber}` : ''}${snapshot.pullRequestNumber ? ` / PR #${snapshot.pullRequestNumber}` : ''}`, + `- **Evento:** ${snapshot.event}${snapshot.action ? ` (${snapshot.action})` : ''}`, + `- **Rama:** ${snapshot.branch ?? 'desconocida'}`, + `- **Ciclo de vida:** ${snapshot.lifecycle ?? 'sin definir'}`, + `- **Esperando a:** ${snapshot.waitingFor ?? 'sin respuesta humana pendiente'}`, + `- **Política de descripción de PR:** ${snapshot.pullRequestDescriptionMode}`, + `- **Etiquetas de issue:** ${snapshot.issueLabels.length > 0 ? snapshot.issueLabels.join(', ') : 'ninguna'}`, + `- **Etiquetas de PR:** ${snapshot.pullRequestLabels.length > 0 ? snapshot.pullRequestLabels.join(', ') : 'ninguna'}`, + ]; + if (snapshot.findingStateEvidence === 'invalid') { + lines.push('- **Hallazgos de Bugbot:** evidencia no válida; revisa el resultado del workflow.'); + } else if (snapshot.findingStates) { + lines.push(`- **Hallazgos de Bugbot:** ${snapshot.findingStates.open} abiertos, ${snapshot.findingStates.reopened} reabiertos, ${snapshot.findingStates.verificationRequired} requieren verificación, ${snapshot.findingStates.unknown} desconocidos, ${snapshot.findingStates.resolved} resueltos`); + } + return lines.join('\n'); +} diff --git a/src/application/ports/issue_lifecycle_ports.ts b/src/application/ports/issue_lifecycle_ports.ts index 4e3563f6e..b92d6713a 100644 --- a/src/application/ports/issue_lifecycle_ports.ts +++ b/src/application/ports/issue_lifecycle_ports.ts @@ -17,6 +17,16 @@ export interface BoundIssueNotificationPort { addComment(issueNumber: number, comment: string): Promise; } +/** Native issue-state authority without conversation publication capability. */ +export interface BoundIssueReopenPort { + openIssue(issueNumber: number): Promise; +} + +/** Native issue-state authority without conversation publication capability. */ +export interface BoundIssueStatePort { + closeIssue(issueNumber: number): Promise; +} + /** Repository-credential-bound issue closure and notification authority. */ export interface BoundIssueClosurePort extends BoundIssueNotificationPort { closeIssue(issueNumber: number): Promise; @@ -45,8 +55,3 @@ export interface BoundIssueCommentPublicationPort { updateComment(issueNumber: number, commentId: number, comment: string): Promise; listIssueComments(issueNumber: number): Promise; } - -/** Bound push notification authority, including the optional reopen transition. */ -export interface BoundIssuePushNotificationPort extends BoundIssueNotificationPort { - openIssue(issueNumber: number): Promise; -} diff --git a/src/application/usecases/__tests__/push_single_action_contexts.test.ts b/src/application/usecases/__tests__/push_single_action_contexts.test.ts index 306ad594f..6d8891cd4 100644 --- a/src/application/usecases/__tests__/push_single_action_contexts.test.ts +++ b/src/application/usecases/__tests__/push_single_action_contexts.test.ts @@ -201,6 +201,7 @@ describe('push and single-action context projection', () => { waitingLabels: ['state:awaiting-maintainer', 'state:awaiting-issue-author'], activityLabel: 'state:ai-processing', thresholdHours: 168, + locale: 'en-US', }); }); @@ -226,6 +227,7 @@ describe('push and single-action context projection', () => { pushedBranch: 'feature/42-contexts', deletedPush: false, repository: { owner: 'owner', name: 'repo' }, + locale: 'en-US', }); }); diff --git a/src/application/usecases/actions/__tests__/close_inactive_issues_use_case.test.ts b/src/application/usecases/actions/__tests__/close_inactive_issues_use_case.test.ts index d96cb4eaa..b046678e2 100644 --- a/src/application/usecases/actions/__tests__/close_inactive_issues_use_case.test.ts +++ b/src/application/usecases/actions/__tests__/close_inactive_issues_use_case.test.ts @@ -71,8 +71,9 @@ describe('CloseInactiveIssuesUseCase', () => { expect(closeIssue).toHaveBeenCalledWith(42); expect(addComment).toHaveBeenCalledWith( 42, - expect.stringContaining('automatically closed due to inactivity'), + expect.stringContaining('## Issue closed after inactivity'), ); + expect(addComment.mock.calls[0][1]).toContain('topic="inactivity" target="issue:42"'); expect(result).toMatchObject({ id: 'CloseInactiveIssuesUseCase', success: true, @@ -81,6 +82,15 @@ describe('CloseInactiveIssuesUseCase', () => { }); }); + it('uses the configured issue locale for the terminal explanation', async () => { + listOpenIssuesByLabel.mockResolvedValueOnce([snapshot()]); + + await createUseCase().invoke(execution({ locale: { issue: 'es-MX' } })); + + expect(addComment.mock.calls[0][1]).toContain('## Issue cerrada por inactividad'); + expect(addComment.mock.calls[0][1]).toContain('168 horas'); + }); + it('does not close pull requests or recently active issues', async () => { listOpenIssuesByLabel .mockResolvedValueOnce([snapshot({ isPullRequest: true })]) diff --git a/src/application/usecases/actions/__tests__/publish_issue_comment_use_case.test.ts b/src/application/usecases/actions/__tests__/publish_issue_comment_use_case.test.ts index 80dbee1db..66c781878 100644 --- a/src/application/usecases/actions/__tests__/publish_issue_comment_use_case.test.ts +++ b/src/application/usecases/actions/__tests__/publish_issue_comment_use_case.test.ts @@ -1,7 +1,6 @@ import type { Execution } from '../../../../data/model/execution'; import { ACTIONS } from '../../../../data/model/action_types'; import { SingleAction } from '../../../../data/model/single_action'; -import { getCommentWatermark } from '../../../../utils/comment_watermark'; import { PublishIssueCommentUseCase } from '../publish_issue_comment_use_case'; import { projectIssueCommentActionContext } from '../../push_single_action_contexts'; @@ -57,7 +56,7 @@ describe('PublishIssueCommentUseCase', () => { it('appends after existing content without duplicating its Copilot watermark', async () => { listIssueComments.mockResolvedValue([{ id: 101, - body: `Deployment started.\n\n${getCommentWatermark()}`, + body: 'Deployment started.\n\nMade with ❤️ by [vypdev/copilot](https://github.com/marketplace/actions/copilot-github-with-super-powers)', }]); const results = await useCase.invoke(execution('Deployment failed.', '101', 'append')); diff --git a/src/application/usecases/actions/close_inactive_issues_workflow.ts b/src/application/usecases/actions/close_inactive_issues_workflow.ts index 74d1e5537..5453eaa26 100644 --- a/src/application/usecases/actions/close_inactive_issues_workflow.ts +++ b/src/application/usecases/actions/close_inactive_issues_workflow.ts @@ -5,6 +5,8 @@ import type { BoundIssueInactivityQueryPort, IssueInactivityClockPort } from '.. import type { InactivityContext } from '../push_single_action_contexts'; import { logDebugInfo, logError, logInfo } from '../../ports/logging_ports'; import { ApplicationError, toApplicationError } from '../../errors/application_error'; +import { baseLanguage } from '../../../domain/locale'; +import { buildPublicationMarker, createSemanticDigest } from '../../policies/publication_identity_policy'; export interface CloseInactiveIssuesWorkflowDependencies { readonly issueQueryPort: BoundIssueInactivityQueryPort; @@ -13,9 +15,6 @@ export interface CloseInactiveIssuesWorkflowDependencies { } const TASK_ID = 'CloseInactiveIssuesUseCase'; -const INACTIVITY_COMMENT = (thresholdHours: number): string => - `This issue was automatically closed due to inactivity while waiting for a response. No activity was detected for at least **${thresholdHours} hours**. Reopen it and add a comment if it still needs attention.`; - /** Scans waiting issues and closes only candidates that remain inactive. */ export async function runCloseInactiveIssuesWorkflow( param: InactivityContext, @@ -77,7 +76,7 @@ export async function runCloseInactiveIssuesWorkflow( closedCount++; await dependencies.issueClosurePort.addComment( candidate.number, - INACTIVITY_COMMENT(thresholdHours), + buildInactivityExplanation(candidate, thresholdHours, param.locale), ); logInfo(`Issue #${candidate.number} closed after inactivity.`); } catch (error) { @@ -116,6 +115,39 @@ export async function runCloseInactiveIssuesWorkflow( } } +function buildInactivityExplanation( + candidate: IssueActivitySnapshot, + thresholdHours: number, + locale: string, +): string { + const digest = createSemanticDigest({ updatedAt: candidate.updatedAt, thresholdHours }); + const marker = buildPublicationMarker({ + identity: { topic: 'inactivity', target: { kind: 'issue', number: candidate.number }, key: 'closure' }, + sourceVersion: `policy:${digest}`, + digest, + }); + if (baseLanguage(locale) === 'es') { + return [ + marker, + '', + '## Issue cerrada por inactividad', + '', + `No se detectó actividad durante al menos **${thresholdHours} horas** mientras esta issue esperaba una respuesta.`, + '', + 'Si todavía necesita atención, vuelve a abrirla y añade un comentario con el contexto actualizado.', + ].join('\n'); + } + return [ + marker, + '', + '## Issue closed after inactivity', + '', + `No activity was detected for at least **${thresholdHours} hours** while this issue was waiting for a response.`, + '', + 'If it still needs attention, reopen it and add a comment with the current context.', + ].join('\n'); +} + async function listCandidates( _param: InactivityContext, waitingLabels: readonly string[], diff --git a/src/application/usecases/actions/observe_branch_sync_use_case.ts b/src/application/usecases/actions/observe_branch_sync_use_case.ts index 1b0f44941..d67674e64 100644 --- a/src/application/usecases/actions/observe_branch_sync_use_case.ts +++ b/src/application/usecases/actions/observe_branch_sync_use_case.ts @@ -77,6 +77,7 @@ export class ObserveBranchSyncUseCase implements ParamUseCase { 'owner', 'repo', 200, - expect.stringContaining('Bugbot status'), + expect.stringContaining('Bugbot: review needs verification'), 'token', { commitSha: 'sha1' }, ); diff --git a/src/application/usecases/steps/commit/__tests__/notify_new_commit_on_issue_use_case.test.ts b/src/application/usecases/steps/commit/__tests__/notify_new_commit_on_issue_use_case.test.ts index 870f53e50..4f1aca46a 100644 --- a/src/application/usecases/steps/commit/__tests__/notify_new_commit_on_issue_use_case.test.ts +++ b/src/application/usecases/steps/commit/__tests__/notify_new_commit_on_issue_use_case.test.ts @@ -7,231 +7,56 @@ jest.mock('../../../../../utils/logger', () => ({ logError: jest.fn(), })); -jest.mock('../../../../../utils/list_utils', () => ({ - getRandomElement: jest.fn((arr: string[]) => arr[0]), -})); - -const mockAddComment = jest.fn(); -const mockOpenIssue = jest.fn(); - -const mockBuildCommitPrefix = jest.fn(); -jest.mock('../../common/execute_script_use_case', () => ({ - buildCommitPrefix: (...args: unknown[]) => mockBuildCommitPrefix(...args), -})); +const openIssue = jest.fn(); -function baseParam(overrides: Record = {}) { +function context(reopenOnPush: boolean) { return projectCommitNotificationContext({ - owner: 'o', - repo: 'r', issueNumber: 42, - tokens: { token: 't' }, commit: { branch: 'feature/42-add-login', - commits: [ - { - id: 'abc', - message: 'feat: add button', - author: { name: 'Alice', username: 'alice' }, - }, - ], + commits: [{ id: 'abc', message: 'feat: add button', author: { name: 'Alice', username: 'alice' } }], }, - commitPrefixBuilder: '', - images: { - imagesOnCommit: true, - commitReleaseGifs: ['url1'], - commitHotfixGifs: ['url2'], - commitBugfixGifs: ['url3'], - commitFeatureGifs: ['url4'], - commitDocsGifs: ['url5'], - commitChoreGifs: ['url6'], - commitAutomaticActions: ['url7'], - }, - issue: { reopenOnPush: false }, + commitPrefixBuilder: 'replace-slash', + images: { imagesOnCommit: true, commitFeatureGifs: ['decorative.gif'] }, + issue: { reopenOnPush }, release: { active: false }, hotfix: { active: false }, isFeature: true, isBugfix: false, isDocs: false, isChore: false, - ...overrides, } as never); } -describe('NotifyNewCommitOnIssueUseCase', () => { - let useCase: NotifyNewCommitOnIssueUseCase; - +describe('NotifyNewCommitOnIssueUseCase quiet push policy', () => { beforeEach(() => { - useCase = new NotifyNewCommitOnIssueUseCase({ openIssue: mockOpenIssue, addComment: mockAddComment }); - mockAddComment.mockResolvedValue(undefined); - mockOpenIssue.mockResolvedValue(true); - mockBuildCommitPrefix.mockReturnValue(''); + jest.clearAllMocks(); + openIssue.mockResolvedValue(true); }); - it('adds comment with commit info and returns', async () => { - const param = baseParam(); - const results = await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringContaining('Feature News'), - ); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringContaining('feature/42-add-login'), - ); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringContaining('alice'), - ); - expect(results).toEqual([]); - }); - - it('does not call openIssue when reopenOnPush is false', async () => { - const param = baseParam({ issue: { reopenOnPush: false } }); - await useCase.invoke(param); - expect(mockOpenIssue).not.toHaveBeenCalled(); - }); + it('creates no comment and performs no state mutation for a routine push', async () => { + const port = { openIssue }; + const results = await new NotifyNewCommitOnIssueUseCase(port).invoke(context(false)); - it('calls openIssue and addComment when reopenOnPush is true', async () => { - const param = baseParam({ issue: { reopenOnPush: true } }); - await useCase.invoke(param); - expect(mockOpenIssue).toHaveBeenCalledWith(42); - expect(mockAddComment).toHaveBeenCalled(); - }); - - it('returns failure on error', async () => { - mockAddComment.mockRejectedValue(new Error('API error')); - const param = baseParam(); - const results = await useCase.invoke(param); - expect(results.some((r) => r.success === false)).toBe(true); - expect(results[0].errors?.length).toBeGreaterThan(0); - }); - - it('builds and uses the commit prefix when commitPrefixBuilder is set', async () => { - mockBuildCommitPrefix.mockReturnValue('feature-42-add-login'); - const param = baseParam({ - commitPrefixBuilder: 'replace-slash', - commitPrefixBuilderParams: undefined, - commit: { - branch: 'feature/42-add-login', - commits: [ - { - id: 'x', - message: 'feature-42-add-login: add login screen', - author: { name: 'A', username: 'a' }, - }, - ], - }, - }); - await useCase.invoke(param); - expect(mockBuildCommitPrefix).toHaveBeenCalledWith( - 'feature/42-add-login', - 'replace-slash', - ); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringContaining('add login screen') - ); - }); - - it('uses release title when release.active', async () => { - const param = baseParam({ release: { active: true }, isFeature: false }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringContaining('Release News') - ); - }); - - it('uses hotfix title when hotfix.active', async () => { - const param = baseParam({ hotfix: { active: true }, isFeature: false }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringContaining('Hotfix News') - ); - }); - - it('uses bugfix and docs titles', async () => { - const paramBugfix = baseParam({ isBugfix: true, isFeature: false }); - await useCase.invoke(paramBugfix); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringContaining('Bugfix News') - ); - - const paramDocs = baseParam({ isDocs: true, isFeature: false }); - await useCase.invoke(paramDocs); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringContaining('Documentation News') - ); + expect(results).toEqual([]); + expect(openIssue).not.toHaveBeenCalled(); + expect(port).not.toHaveProperty('addComment'); }); - it('uses chore and Automatic News titles', async () => { - const paramChore = baseParam({ isChore: true, isFeature: false }); - await useCase.invoke(paramChore); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringContaining('Chore News') - ); + it.each([true, false])('uses only native reopen state when the provider returns %s', async (opened) => { + openIssue.mockResolvedValue(opened); + const results = await new NotifyNewCommitOnIssueUseCase({ openIssue }).invoke(context(true)); - const paramAuto = baseParam({ - isFeature: false, - isBugfix: false, - isDocs: false, - isChore: false, - }); - await useCase.invoke(paramAuto); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringContaining('Automatic News') - ); + expect(results).toEqual([]); + expect(openIssue).toHaveBeenCalledWith(42); }); - it('adds Attention section when commit does not start with prefix and commitPrefix is set', async () => { - mockBuildCommitPrefix.mockReturnValue('feature-42'); - const param = baseParam({ - commitPrefixBuilder: 'replace-slash', - commit: { - branch: 'feature/42-add-login', - commits: [ - { - id: 'x', - message: 'wrong prefix: something', - author: { name: 'A', username: 'a' }, - }, - ], - }, - }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringContaining('Attention') - ); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringMatching(/prefix \*\*feature-42\*\*/) - ); - }); + it('returns semantic operator evidence when native state synchronization fails', async () => { + openIssue.mockRejectedValue(new Error('API error')); - it('when reopenOnPush and openIssue returns true, adds re-opened comment first', async () => { - mockOpenIssue.mockResolvedValue(true); - const param = baseParam({ issue: { reopenOnPush: true } }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringContaining('re-opened after pushing new commits') - ); - }); + const results = await new NotifyNewCommitOnIssueUseCase({ openIssue }).invoke(context(true)); - it('when reopenOnPush and openIssue returns false, does not add re-opened comment', async () => { - mockAddComment.mockClear(); - mockOpenIssue.mockResolvedValue(false); - const param = baseParam({ issue: { reopenOnPush: true } }); - await useCase.invoke(param); - const reOpenedCalls = mockAddComment.mock.calls.filter((c) => - c[1].includes('re-opened after pushing') - ); - expect(reOpenedCalls).toHaveLength(0); + expect(results[0]).toMatchObject({ success: false, executed: true }); + expect(results[0].errors[0]).toMatchObject({ code: 'provider.unavailable' }); }); }); diff --git a/src/application/usecases/steps/commit/bugbot/__tests__/mark_findings_resolved_use_case.test.ts b/src/application/usecases/steps/commit/bugbot/__tests__/mark_findings_resolved_use_case.test.ts index 591d5766b..e74e84d31 100644 --- a/src/application/usecases/steps/commit/bugbot/__tests__/mark_findings_resolved_use_case.test.ts +++ b/src/application/usecases/steps/commit/bugbot/__tests__/mark_findings_resolved_use_case.test.ts @@ -6,7 +6,6 @@ import type { BugbotContext, } from "../types"; import type { ExistingByFindingId } from '../../../../../../domain/bugbot/finding'; -import { getCommentWatermark } from "../../../../../../utils/comment_watermark"; import { buildMarker } from '../../../../../policies/bugbot_finding_marker_policy'; import type { BugbotContextSelectionContext } from '../bugbot_review_operation_context'; @@ -258,7 +257,7 @@ describe("markFindingsResolved", () => { }); it("updates from the full issue body and removes its old trailing watermark", async () => { - const fullBody = `${unresolvedBody}\n\n${"x".repeat(15000)}\n\n${getCommentWatermark()}`; + const fullBody = `${unresolvedBody}\n\n${"x".repeat(15000)}\n\nMade with ❤️ by [vypdev/copilot](https://github.com/marketplace/actions/copilot-github-with-super-powers)`; const errors = await markFindingsResolved({ operation: baseOperation(), diff --git a/src/application/usecases/steps/commit/bugbot/__tests__/reconcile_bugbot_review_state_integration.test.ts b/src/application/usecases/steps/commit/bugbot/__tests__/reconcile_bugbot_review_state_integration.test.ts index e3e0206e3..58a5f5856 100644 --- a/src/application/usecases/steps/commit/bugbot/__tests__/reconcile_bugbot_review_state_integration.test.ts +++ b/src/application/usecases/steps/commit/bugbot/__tests__/reconcile_bugbot_review_state_integration.test.ts @@ -424,7 +424,7 @@ describe('Bugbot review reconciliation integration', () => { 'org', 'repo', 358, - expect.stringContaining('Unknown | 2'), + expect.stringContaining('2 finding(s) have unknown state'), 'token', { commitSha: head }, ); @@ -784,7 +784,7 @@ describe('Bugbot review reconciliation integration', () => { expect(report?.projection.actionableCount).toBe(0); expect(report?.projection.findings[0]?.id).toBe('malformed-comment-PRRC_malformed'); expect(test.addComment).toHaveBeenCalledWith( - 'org', 'repo', 358, expect.stringContaining('Unknown | 1'), 'token', { commitSha: head }, + 'org', 'repo', 358, expect.stringContaining('1 finding(s) have unknown state'), 'token', { commitSha: head }, ); }); @@ -1014,7 +1014,7 @@ describe('Bugbot review reconciliation integration', () => { expect(report?.statusCardOperation).toBe('updated'); expect(test.updateComment).toHaveBeenCalledTimes(2); expect(test.updateComment).toHaveBeenCalledWith( - 'org', 'repo', 358, 11, expect.stringContaining('no longer current'), 'token', { commitSha: head }, + 'org', 'repo', 358, 11, expect.stringContaining('superseded by the canonical card'), 'token', { commitSha: head }, ); }); diff --git a/src/application/usecases/steps/commit/bugbot/__tests__/synchronize_bugbot_review_presentation_use_case.test.ts b/src/application/usecases/steps/commit/bugbot/__tests__/synchronize_bugbot_review_presentation_use_case.test.ts index 5f492b4ed..a107e3e76 100644 --- a/src/application/usecases/steps/commit/bugbot/__tests__/synchronize_bugbot_review_presentation_use_case.test.ts +++ b/src/application/usecases/steps/commit/bugbot/__tests__/synchronize_bugbot_review_presentation_use_case.test.ts @@ -100,7 +100,7 @@ describe('synchronizeBugbotReviewPresentation', () => { expect(result.projection.outcome).toBe('complete'); expect(test.addComment).toHaveBeenCalledWith( 10, - expect.stringContaining('No active findings'), + expect.stringContaining('Bugbot: review complete'), { commitSha: head }, ); }); @@ -232,7 +232,7 @@ describe('synchronizeBugbotReviewPresentation', () => { expect(test.updateComment).toHaveBeenCalledWith( 10, 3, - expect.stringContaining('no longer current'), + expect.stringContaining('superseded by the canonical card'), { commitSha: head }, ); }); diff --git a/src/application/usecases/steps/commit/bugbot/publish_findings_use_case.ts b/src/application/usecases/steps/commit/bugbot/publish_findings_use_case.ts index 0a41706ff..90ffd0ef8 100644 --- a/src/application/usecases/steps/commit/bugbot/publish_findings_use_case.ts +++ b/src/application/usecases/steps/commit/bugbot/publish_findings_use_case.ts @@ -4,7 +4,6 @@ */ import type { BugbotFindingPublicationPorts } from "../../../../../application/ports/bugbot_finding_publication_ports"; -import { getCommentWatermark } from "../../../../../utils/comment_watermark"; import type { BugbotContext } from "./types"; import { findExistingFindingInfo, @@ -19,7 +18,7 @@ export interface PublishFindingsParam { operation: BugbotReviewOperationContext; context: BugbotContext; findings: BugbotFinding[]; - /** Commit SHA for bugbot watermark (commit link). When set, comment uses "for commit ..." watermark. */ + /** Commit SHA retained as provider metadata for compatible issue adapters. */ commitSha?: string; /** When findings were limited by max comments, add one summary comment with this overflow info. */ overflowCount?: number; @@ -31,11 +30,6 @@ export async function publishFindings(param: PublishFindingsParam): Promise 0) rows.push(`| — | ${omittedRuleCount} omitted by duplicate, empty, or combined-budget policy |`); sections.push(`### Review configuration\n\nRules in effective precedence order:\n\n| Source | Status |\n| --- | --- |\n${rows.join('\n')}`); } - sections.push(watermark); return sections.join("\n\n"); } diff --git a/src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts b/src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts index 4884a69f6..ee8b8cee9 100644 --- a/src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts +++ b/src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts @@ -17,6 +17,8 @@ import { type OwnedBugbotReview, } from '../../../../policies/bugbot_review_ownership_policy'; import { buildBugbotReviewProjection } from '../../../../../domain/bugbot/review_projection'; +import { buildDuplicateMarker } from '../../../../policies/publication_identity_policy'; +import { resolveStaticPublicationCatalog } from '../../../../policies/publication_message_catalog'; const MAX_REVIEW_UPDATES_PER_RUN = 20; const REVIEW_UPDATE_CONCURRENCY = 4; @@ -170,13 +172,16 @@ async function synchronizeStatusCard( trustedStatusComments.slice(1), REVIEW_UPDATE_CONCURRENCY, async (duplicate) => { + const messages = resolveStaticPublicationCatalog(input.target.locale).catalog; await input.ports.comments.updateComment( input.target.pullRequestNumber, duplicate.id, [ - '## 🤖 Bugbot status moved', + buildDuplicateMarker(canonical?.id ?? duplicate.id), '', - `This duplicate status card is no longer current. [Use the canonical PR status](${navigation.pullRequestUrl}).`, + messages.supersededStatus, + '', + `[${messages.viewCurrentStatus}](${navigation.pullRequestUrl}).`, ].join('\n'), { commitSha: input.snapshot.verifiedHeadSha }, ); diff --git a/src/application/usecases/steps/commit/commit_notification_content_policy.ts b/src/application/usecases/steps/commit/commit_notification_content_policy.ts deleted file mode 100644 index b8419aed1..000000000 --- a/src/application/usecases/steps/commit/commit_notification_content_policy.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { CommitNotificationContext } from '../../push_single_action_contexts'; -import { getRandomElement } from "../../../../utils/list_utils"; - -const SEPARATOR = "------------------------------------------------------"; - -interface CommitNotificationTheme { - title: string; - image: string | undefined; -} - -export interface CommitNotificationContent { - body: string; - shouldWarn: boolean; -} - -export function buildCommitNotificationContent( - param: CommitNotificationContext, - commitPrefix: string, -): CommitNotificationContent { - const theme = resolveTheme(param); - let body = ` -# ${theme.title} - -**Changes on branch \`${param.branch}\`:** - -`; - let shouldWarn = false; - - for (const commit of param.commits) { - const commitMessage = commit.message ?? ""; - body += ` -${SEPARATOR} - -- ${commit.id ?? "unknown"} by **${commit.author?.name ?? "unknown"}** (@${commit.author?.username ?? "unknown"}) -\`\`\` -${commitMessage.split(`${commitPrefix}: `).join("")} -\`\`\` - -`; - if (hasUnexpectedPrefix(commitMessage, commitPrefix)) shouldWarn = true; - } - - if (shouldWarn && commitPrefix.length > 0) { - body += ` -${SEPARATOR} -## ⚠️ Attention - -One or more commits didn't start with the prefix **${commitPrefix}**. - -\`\`\` -${commitPrefix}: created hello-world app -\`\`\` -`; - } - - if (theme.image && param.imagesOnCommit) { - body += ` -${SEPARATOR} - -![image](${theme.image}) -`; - } - return { body, shouldWarn }; -} - -function resolveTheme(param: CommitNotificationContext): CommitNotificationTheme { - const titles: Record = { - release: '🚀 Release News', - hotfix: '🔥🐛 Hotfix News', - bugfix: '🐛 Bugfix News', - feature: '✨ Feature News', - docs: '📝 Documentation News', - chore: '🔧 Chore News', - automatic: '🪄 Automatic News', - }; - return { title: titles[param.theme], image: getRandomElement([...param.themeImages]) }; -} - -function hasUnexpectedPrefix(commitMessage: string, commitPrefix: string): boolean { - return commitPrefix.length > 0 - && !commitMessage.startsWith(commitPrefix) - && !commitMessage.startsWith("Merge branch ") - && !commitMessage.startsWith("gh-action: "); -} diff --git a/src/application/usecases/steps/commit/notify_new_commit_on_issue_use_case.ts b/src/application/usecases/steps/commit/notify_new_commit_on_issue_use_case.ts index ec0188a5c..bc817a596 100644 --- a/src/application/usecases/steps/commit/notify_new_commit_on_issue_use_case.ts +++ b/src/application/usecases/steps/commit/notify_new_commit_on_issue_use_case.ts @@ -1,5 +1,5 @@ import { Result } from "../../../../data/model/result"; -import type { BoundIssuePushNotificationPort } from "../../../../application/ports/issue_lifecycle_ports"; +import type { BoundIssueReopenPort } from "../../../../application/ports/issue_lifecycle_ports"; import type { CommitNotificationContext } from '../../push_single_action_contexts'; import { logInfo } from "../../../ports/logging_ports"; import { getTaskEmoji } from "../../../../utils/task_emoji"; @@ -9,7 +9,7 @@ import { runNotifyNewCommitOnIssueWorkflow } from "./notify_new_commit_on_issue_ export class NotifyNewCommitOnIssueUseCase implements ParamUseCase { taskId: string = "NotifyNewCommitOnIssueUseCase"; - constructor(private readonly issueRepository: BoundIssuePushNotificationPort) {} + constructor(private readonly issueRepository: BoundIssueReopenPort) {} async invoke(param: CommitNotificationContext): Promise { logInfo(`${getTaskEmoji(this.taskId)} Executing ${this.taskId}.`); diff --git a/src/application/usecases/steps/commit/notify_new_commit_on_issue_workflow.ts b/src/application/usecases/steps/commit/notify_new_commit_on_issue_workflow.ts index 3d48cfd13..9c2790256 100644 --- a/src/application/usecases/steps/commit/notify_new_commit_on_issue_workflow.ts +++ b/src/application/usecases/steps/commit/notify_new_commit_on_issue_workflow.ts @@ -1,51 +1,33 @@ import { Result } from "../../../../data/model/result"; -import type { BoundIssuePushNotificationPort } from "../../../../application/ports/issue_lifecycle_ports"; +import type { BoundIssueReopenPort } from "../../../../application/ports/issue_lifecycle_ports"; import type { CommitNotificationContext } from '../../push_single_action_contexts'; -import { logDebugInfo, logError } from "../../../ports/logging_ports"; -import { buildCommitPrefix } from "../common/execute_script_use_case"; -import { buildCommitNotificationContent } from "./commit_notification_content_policy"; +import { logError, logInfo } from "../../../ports/logging_ports"; import { toApplicationError } from "../../../errors/application_error"; export async function runNotifyNewCommitOnIssueWorkflow( param: CommitNotificationContext, taskId: string, - issueRepository: BoundIssuePushNotificationPort, + issueRepository: BoundIssueReopenPort, ): Promise { const result: Result[] = []; try { - const branchName = param.branch; - let commitPrefix = ""; - if (param.commitPrefixBuilder.length > 0) { - commitPrefix = buildCommitPrefix(branchName, param.commitPrefixBuilder); - logDebugInfo(`Commit prefix: ${commitPrefix}`); - } - - const { body } = buildCommitNotificationContent(param, commitPrefix); if (param.reopenOnPush) { const opened = await issueRepository.openIssue( param.issueNumber, ); - if (opened) { - await issueRepository.addComment( - param.issueNumber, - `This issue was re-opened after pushing new commits to the branch \`${branchName}\`.`, - ); - } + logInfo(opened + ? `Issue #${param.issueNumber} reopened after a push; native state is sufficient.` + : `Issue #${param.issueNumber} was already open; no conversation notification was created.`); } - - await issueRepository.addComment( - param.issueNumber, - body, - ); } catch (error) { - const semanticError = toApplicationError(error, 'provider.unavailable', 'Unable to notify the issue about the new commit.'); + const semanticError = toApplicationError(error, 'provider.unavailable', 'Unable to synchronize the issue state after the new commit.'); logError(semanticError); result.push( new Result({ id: taskId, success: false, executed: true, - steps: ["Tried to notify the new commit on the issue, but there was a problem."], + steps: ["Tried to synchronize issue state after the new commit, but there was a problem."], errors: [semanticError], }), ); diff --git a/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts b/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts index 972cc2230..96a70d107 100644 --- a/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts +++ b/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts @@ -1,509 +1,179 @@ import { Result } from '../../../../../data/model/result'; import { PublishResultUseCase } from '../publish_resume_use_case'; -import { ApplicationError } from '../../../../errors/application_error'; -import { - projectPublishResultContext, - type PublishResultContextSource, -} from '../publish_resume_workflow'; - -const mockGetAccumulatedLogsAsText = jest.fn(() => ''); -jest.mock('../../../../ports/logging_ports', () => ({ - logInfo: jest.fn(), - logError: jest.fn(), - getAccumulatedLogsAsText: () => mockGetAccumulatedLogsAsText(), -})); - -const mockGetRandomElement = jest.fn(() => undefined); -jest.mock('../../../../../utils/list_utils', () => ({ - getRandomElement: (values: readonly string[]) => mockGetRandomElement(values), -})); +import { projectPublishResultContext, type PublishResultContextSource } from '../publish_resume_workflow'; +import type { IssueCommentPublicationTarget } from '../../../../ports/issue_lifecycle_ports'; + +function recommendation(steps = '1. Add the policy\n2. Add tests', fingerprint = 'a'.repeat(16)): Result { + return new Result({ + id: 'RecommendStepsUseCase', success: true, executed: true, + payload: { + issueNumber: 42, + recommendedSteps: steps, + recommendationState: { issueDescriptionFingerprint: fingerprint }, + }, + steps: ['legacy plan wrapper that must never be published'], + }); +} -const mockAddComment = jest.fn(); -const logReport = { - getAccumulatedLogEntries: jest.fn(() => []), - getAccumulatedLogsAsText: () => mockGetAccumulatedLogsAsText(), - clearAccumulatedLogs: jest.fn(), -}; +function progress(value = 65, summary = 'Core behavior is implemented.', remaining = 'Finish validation.'): Result { + return new Result({ + id: 'CheckProgressUseCase', success: true, executed: true, + payload: { issueNumber: 42, progress: value, summary, remaining, branch: 'feature/work', developmentBranch: 'develop' }, + steps: ['legacy progress wrapper that must never be published'], + }); +} -function baseImages(): PublishResultContextSource['images'] { +function source(results: Result[], overrides: Partial = {}): PublishResultContextSource { return { - imagesOnIssue: true, - imagesOnPullRequest: true, - issueAutomaticActions: [], - issueReleaseGifs: [], - issueHotfixGifs: [], - issueBugfixGifs: [], - issueFeatureGifs: [], - issueDocsGifs: [], - issueChoreGifs: [], - pullRequestReleaseGifs: [], - pullRequestHotfixGifs: [], - pullRequestBugfixGifs: [], - pullRequestFeatureGifs: [], - pullRequestDocsGifs: [], - pullRequestChoreGifs: [], - pullRequestAutomaticActions: [], + owner: 'acme', repo: 'widgets', tokenUser: 'vypbot', isPullRequest: false, + eventName: 'issues', issueNumber: 42, issue: { number: 42 }, pullRequest: { number: -1 }, inputs: { action: 'opened' }, + locale: { issue: 'en-US', pullRequest: 'en-US' }, + currentConfiguration: { results }, + ...overrides, }; } -function baseParam(overrides: Partial = {}) { - const defaultConfig = { results: [new Result({ id: 'x', success: true, executed: true, steps: ['Step 1'] })] }; - return projectPublishResultContext({ - issueNumber: 42, - issue: { number: 42 }, - pullRequest: { number: 99, action: 'opened' }, - isIssue: false, - isPullRequest: false, - isPush: false, - isSingleAction: false, - isBugfix: false, - isFeature: false, - isDocs: false, - isChore: false, - currentConfiguration: defaultConfig, - images: baseImages(), - singleAction: { issue: 123 }, - release: { active: false }, - hotfix: { active: false }, - issueNotBranched: false, - debug: false, - ...overrides, - }); +function inMemoryComments(initial: IssueCommentPublicationTarget[] = []) { + const values = initial.map(comment => ({ ...comment })); + let nextId = Math.max(0, ...values.map(comment => comment.id)) + 1; + return { + values, + addComment: jest.fn(async (_issueNumber: number, body: string) => { + values.push({ id: nextId++, body, user: { login: 'vypbot' } }); + }), + updateComment: jest.fn(async (_issueNumber: number, id: number, body: string) => { + const comment = values.find(candidate => candidate.id === id); + if (comment) comment.body = body; + }), + listIssueComments: jest.fn(async () => values.map(comment => ({ ...comment }))), + }; } -describe('PublishResultUseCase', () => { - let useCase: PublishResultUseCase; - - beforeEach(() => { - useCase = new PublishResultUseCase( - { addComment: mockAddComment }, - logReport, - ); - mockAddComment.mockReset(); - mockGetAccumulatedLogsAsText.mockReturnValue(''); - mockGetRandomElement.mockReset().mockReturnValue(undefined); - }); - - it('does not call addComment when content is empty (no steps in results)', async () => { - const param = baseParam({ - isIssue: true, - currentConfiguration: { results: [new Result({ id: 'x', success: true, executed: true, steps: [] })] }, - }); - - await useCase.invoke(param); - - expect(mockAddComment).not.toHaveBeenCalled(); - }); - - it('does not publish a GIF-only comment when debug logs are the only output', async () => { - mockGetAccumulatedLogsAsText.mockReturnValue('[INFO] no-op'); - const param = baseParam({ - isIssue: true, - debug: true, - release: { active: true }, - images: { - ...baseImages(), - issueReleaseGifs: ['release.gif'], - }, - currentConfiguration: { - results: [new Result({ id: 'x', success: true, executed: false, steps: [] })], - }, - }); - - await useCase.invoke(param); - - expect(mockAddComment).not.toHaveBeenCalled(); - }); - - it('does not publish a reminder-only comment', async () => { - const param = baseParam({ - isIssue: true, - release: { active: true }, - images: { - ...baseImages(), - issueReleaseGifs: ['release.gif'], - }, - currentConfiguration: { - results: [new Result({ id: 'x', success: true, executed: true, steps: [], reminders: ['Remember this'] })], - }, - }); - - await useCase.invoke(param); - - expect(mockAddComment).not.toHaveBeenCalled(); - }); - - it('publishes failures even when a result has no steps', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isIssue: true, - currentConfiguration: { - results: [new Result({ id: 'x', success: false, executed: true, errors: [new ApplicationError('authorization.credential-invalid', 'Agent authentication failed')] })], - }, - }); - - await useCase.invoke(param); - - expect(mockAddComment).toHaveBeenCalledWith(42, expect.stringContaining('Agent authentication failed')); - }); - - it('calls addComment on issue when isIssue and results have steps', async () => { - mockAddComment.mockResolvedValue(undefined); - const resultsWithSteps = [new Result({ id: 'a', success: true, executed: true, steps: ['Step 1'] })]; - const param = baseParam({ isIssue: true, currentConfiguration: { results: resultsWithSteps } }); - - await useCase.invoke(param); - - expect(mockAddComment).toHaveBeenCalledTimes(1); - expect(mockAddComment).toHaveBeenCalledWith(42, expect.stringContaining('1. Step 1')); - }); - - it('preserves markdown result structure instead of numbering every line', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isIssue: true, - currentConfiguration: { - results: [new Result({ - id: 'recommendation', - success: true, - executed: true, - stepFormat: 'markdown', - steps: ['## Recommended implementation steps', '1. Add the module\n2. Add tests\n\n```sh\npnpm test\n```'], - })], - }, - }); - - await useCase.invoke(param); - - const commentBody = mockAddComment.mock.calls[0][1] as string; - expect(commentBody).toContain('# 🪄 Automatic Actions\n## Recommended implementation steps'); - expect(commentBody).toContain('\n1. Add the module\n2. Add tests'); - expect(commentBody).toContain('```sh\npnpm test\n```'); - expect(commentBody).not.toContain('1. ## Recommended implementation steps'); - expect(commentBody).not.toContain('2. 1. Add the module'); - }); - - it('calls addComment on pull request when isPullRequest and results have steps', async () => { - mockAddComment.mockResolvedValue(undefined); - const resultsWithSteps = [new Result({ id: 'a', success: true, executed: true, steps: ['Step 1'] })]; - const param = baseParam({ isPullRequest: true, currentConfiguration: { results: resultsWithSteps } }); - - await useCase.invoke(param); - - expect(mockAddComment).toHaveBeenCalledTimes(1); - expect(mockAddComment).toHaveBeenCalledWith(99, expect.stringContaining('1. Step 1')); - }); - - it('does not duplicate a Bugbot native review with an independent PR comment', async () => { - const param = baseParam({ - isPullRequest: true, - currentConfiguration: { - results: [new Result({ - id: 'DetectPotentialProblemsUseCase', - success: true, - executed: true, - steps: ['Potential problems detection completed.'], - })], - }, - }); - - await useCase.invoke(param); - - expect(param.genericCommentMode).toBe('omit-feature-owned'); - expect(mockAddComment).not.toHaveBeenCalled(); - }); - - it('keeps metadata-only PR edits in the Job Summary without a generic conversation comment', async () => { - mockGetAccumulatedLogsAsText.mockReturnValue('[DEBUG] metadata normalization completed'); - const param = baseParam({ - isPullRequest: true, - pullRequest: { number: 99, action: 'edited' }, - debug: true, - currentConfiguration: { - results: [new Result({ - id: 'SynchronizeLifecycleStateUseCase', - success: true, - executed: true, - steps: ['Waiting state cleared.'], - })], - }, - }); +describe('PublishResultUseCase semantic compatibility boundary', () => { + it.each([ + new Result({ id: 'metadata', success: true, executed: true, steps: ['Waiting state cleared.'] }), + new Result({ id: 'failure', success: false, executed: true, errors: [] }), + new Result({ id: 'reminder', success: true, executed: true, reminders: ['Internal reminder'] }), + new Result({ id: 'Bugbot', success: true, executed: true, steps: ['Review complete'] }), + ])('keeps legacy result evidence out of GitHub conversations', async (result) => { + const comments = inMemoryComments(); - await useCase.invoke(param); + await new PublishResultUseCase(comments).invoke(projectPublishResultContext(source([result]))); - expect(param.genericCommentMode).toBe('omit-metadata-only'); - expect(mockAddComment).not.toHaveBeenCalled(); + expect(comments.listIssueComments).not.toHaveBeenCalled(); + expect(comments.addComment).not.toHaveBeenCalled(); + expect(comments.updateComment).not.toHaveBeenCalled(); }); - it('includes debug log section in comment body when debug is true and logs are present', async () => { - mockAddComment.mockResolvedValue(undefined); - mockGetAccumulatedLogsAsText.mockReturnValue('[INFO] line1\n[WARN] line2'); - const param = baseParam({ - isIssue: true, - debug: true, - currentConfiguration: { results: [new Result({ id: 'a', success: true, executed: true, steps: ['Step 1'] })] }, - }); - - await useCase.invoke(param); - - const commentBody = mockAddComment.mock.calls[0][1] as string; - expect(commentBody).toContain('Debug log'); - expect(commentBody).toContain('[INFO] line1'); - expect(commentBody).toContain('[WARN] line2'); - }); - - it('does not include debug log section when debug is false', async () => { - mockAddComment.mockResolvedValue(undefined); - mockGetAccumulatedLogsAsText.mockReturnValue('[INFO] line1'); - const param = baseParam({ isIssue: true, debug: false }); - - await useCase.invoke(param); - - const commentBody = mockAddComment.mock.calls[0][1] as string; - expect(commentBody).not.toContain('Debug log'); - }); - - it('does not include debug log section when debug is true but accumulated logs are empty', async () => { - mockAddComment.mockResolvedValue(undefined); - mockGetAccumulatedLogsAsText.mockReturnValue(''); - const param = baseParam({ - isIssue: true, - debug: true, - currentConfiguration: { results: [new Result({ id: 'a', success: true, executed: true, steps: ['Step 1'] })] }, - }); - - await useCase.invoke(param); - - const commentBody = mockAddComment.mock.calls[0][1] as string; - expect(commentBody).not.toContain('Debug log'); - }); - - it('returns a failure result without mutating the publication snapshot when addComment throws', async () => { - mockAddComment.mockRejectedValue(new Error('API error')); - const param = baseParam({ isIssue: true }); - const initialLength = param.results.length; - - const failure = await useCase.invoke(param); - - expect(param.results).toHaveLength(initialLength); - expect(failure?.success).toBe(false); - expect(failure?.steps).toContain('Tried to publish the resume, but there was a problem.'); - }); - - it('uses release title and image when isIssue and release.active', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isIssue: true, - release: { active: true }, - currentConfiguration: { results: [new Result({ id: 'a', success: true, executed: true, steps: ['Step'] })] }, - }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith(42, expect.stringContaining('Release Actions')); - }); - - it('renders a selected issue image only when issue images are enabled', async () => { - mockAddComment.mockResolvedValue(undefined); - mockGetRandomElement.mockReturnValue('release.gif'); - const param = baseParam({ - isIssue: true, - release: { active: true }, - images: { ...baseImages(), issueReleaseGifs: ['release.gif'] }, - }); + it('creates one concise implementation-plan card without retired chrome', async () => { + const comments = inMemoryComments(); - await useCase.invoke(param); + await new PublishResultUseCase(comments).invoke(projectPublishResultContext(source([recommendation()]))); - expect(mockAddComment).toHaveBeenCalledWith(42, expect.stringContaining('![image](release.gif)')); + expect(comments.addComment).toHaveBeenCalledTimes(1); + expect(comments.values).toHaveLength(1); + expect(comments.values[0].body).toContain('topic="plan" target="issue:42"'); + expect(comments.values[0].body).toContain('## Implementation plan'); + expect(comments.values[0].body).toContain('1. Add the policy'); + expect(comments.values[0].body).not.toMatch(/Automatic Actions|Feature Actions|Debug log|Happy coding|giphy|Made with/u); }); - it('omits a selected issue image when issue images are disabled', async () => { - mockAddComment.mockResolvedValue(undefined); - mockGetRandomElement.mockReturnValue('release.gif'); - const param = baseParam({ - isIssue: true, - release: { active: true }, - images: { ...baseImages(), imagesOnIssue: false, issueReleaseGifs: ['release.gif'] }, + it('publishes an explicit help reply once without reviving generic step publication', async () => { + const comments = inMemoryComments(); + const result = new Result({ + id: 'Comment.Help', success: true, executed: true, + steps: ['legacy wrapper'], + payload: { publication: { kind: 'help', botLogin: 'vypbot' } }, }); + const value = projectPublishResultContext(source([result], { + eventName: 'issue_comment', inputs: { action: 'created', comment: { id: 99 } }, + })); - await useCase.invoke(param); + await new PublishResultUseCase(comments).invoke(value); + await new PublishResultUseCase(comments).invoke(value); - expect(mockAddComment.mock.calls[0][1]).not.toContain('![image](release.gif)'); + expect(comments.addComment).toHaveBeenCalledTimes(1); + expect(comments.values[0].body).toContain('correlation="comment:99"'); + expect(comments.values[0].body).toContain('## Copilot commands'); + expect(comments.values[0].body).not.toContain('legacy wrapper'); }); - it('renders a selected pull-request image only when pull-request images are enabled', async () => { - mockAddComment.mockResolvedValue(undefined); - mockGetRandomElement.mockReturnValue('pull-request.gif'); - const param = baseParam({ - isPullRequest: true, - images: { ...baseImages(), pullRequestAutomaticActions: ['pull-request.gif'] }, - }); + it('does not mutate an unchanged plan card on replay', async () => { + const comments = inMemoryComments(); + const useCase = new PublishResultUseCase(comments); + const context = projectPublishResultContext(source([recommendation()])); - await useCase.invoke(param); + await useCase.invoke(context); + comments.addComment.mockClear(); + comments.updateComment.mockClear(); + await useCase.invoke(context); - expect(mockAddComment).toHaveBeenCalledWith(99, expect.stringContaining('![image](pull-request.gif)')); + expect(comments.addComment).not.toHaveBeenCalled(); + expect(comments.updateComment).not.toHaveBeenCalled(); + expect(comments.values).toHaveLength(1); }); - it('omits a selected pull-request image when pull-request images are disabled', async () => { - mockAddComment.mockResolvedValue(undefined); - mockGetRandomElement.mockReturnValue('pull-request.gif'); - const param = baseParam({ - isPullRequest: true, - images: { ...baseImages(), imagesOnPullRequest: false, pullRequestAutomaticActions: ['pull-request.gif'] }, - }); + it('updates the same plan card after a material recommendation change', async () => { + const comments = inMemoryComments(); + const useCase = new PublishResultUseCase(comments); - await useCase.invoke(param); + await useCase.invoke(projectPublishResultContext(source([recommendation()]))); + await useCase.invoke(projectPublishResultContext(source([ + recommendation('1. Replace the policy\n2. Extend integration tests', 'b'.repeat(16)), + ]))); - expect(mockAddComment.mock.calls[0][1]).not.toContain('![image](pull-request.gif)'); + expect(comments.addComment).toHaveBeenCalledTimes(1); + expect(comments.updateComment).toHaveBeenCalledTimes(1); + expect(comments.values[0].body).toContain('Replace the policy'); }); - it('uses hotfix title when isIssue and hotfix.active', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isIssue: true, - hotfix: { active: true }, - currentConfiguration: { results: [new Result({ id: 'a', success: true, executed: true, steps: ['Step'] })] }, - }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith(42, expect.stringContaining('Hotfix Actions')); - }); + it.each([ + [0, 'not started'], + [65, 'in progress'], + [100, 'complete'], + ] as const)('renders bounded progress state for %s%%', async (value, state) => { + const comments = inMemoryComments(); - it('uses feature title when isIssue and isFeature', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isIssue: true, - isFeature: true, - currentConfiguration: { results: [new Result({ id: 'a', success: true, executed: true, steps: ['Step'] })] }, - }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith(42, expect.stringContaining('Feature Actions')); - }); + await new PublishResultUseCase(comments).invoke(projectPublishResultContext(source([progress(value)]))); - it('uses docs title when isIssue and isDocs', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isIssue: true, - isDocs: true, - currentConfiguration: { results: [new Result({ id: 'a', success: true, executed: true, steps: ['Step'] })] }, - }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith(42, expect.stringContaining('Documentation Actions')); + expect(comments.values[0].body).toContain(`## Progress: ${value}% — ${state}`); + expect(comments.values[0].body).not.toContain('Reasoning'); }); - it('uses chore title when isPullRequest and isChore', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isPullRequest: true, - isChore: true, - currentConfiguration: { results: [new Result({ id: 'a', success: true, executed: true, steps: ['Step'] })] }, - }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith(99, expect.stringContaining('Chore Actions')); - }); + it('uses the configured issue locale for deterministic Spanish copy', async () => { + const comments = inMemoryComments(); - it('uses Automatic Actions and singleAction.issue when isSingleAction', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isSingleAction: true, - singleAction: { issue: 456 }, - currentConfiguration: { results: [new Result({ id: 'a', success: true, executed: true, steps: ['Step'] })] }, - }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith(456, expect.any(String)); - }); + await new PublishResultUseCase(comments).invoke(projectPublishResultContext(source( + [progress(100)], + { locale: { issue: 'es-MX', pullRequest: 'en-US' } }, + ))); - it('includes reminder section when results have reminders', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isIssue: true, - currentConfiguration: { - results: [ - new Result({ id: 'a', success: true, executed: true, steps: ['Step'], reminders: ['Remind me'] }), - ], - }, - }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith(42, expect.stringContaining('Reminder')); - expect(mockAddComment).toHaveBeenCalledWith(42, expect.stringContaining('1. Remind me')); + expect(comments.values[0].body).toContain('## Progreso: 100% — completado'); + expect(comments.values[0].body).toContain('No se requiere ninguna acción.'); }); - it('includes errors section when results have errors', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isIssue: true, - currentConfiguration: { - results: [ - new Result({ id: 'a', success: true, executed: true, steps: ['Step'], errors: [new ApplicationError('workflow.failed', 'Something failed')] }), - ], - }, - }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith(42, expect.stringContaining('Errors Found')); - }); - - it('calls addComment on issueNumber when isPush and issueNumber > 0', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isPush: true, - issueNumber: 7, - currentConfiguration: { results: [new Result({ id: 'a', success: true, executed: true, steps: ['Step'] })] }, - }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith(7, expect.any(String)); - }); + it('omits semantic publication when the trusted bot identity is unavailable', async () => { + const comments = inMemoryComments(); - it('uses issueNotBranched and Automatic Actions title when isIssue and issueNotBranched', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isIssue: true, - issueNotBranched: true, - currentConfiguration: { results: [new Result({ id: 'a', success: true, executed: true, steps: ['Step'] })] }, - }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith(42, expect.stringContaining('Automatic Actions')); - }); + await new PublishResultUseCase(comments).invoke(projectPublishResultContext(source( + [recommendation()], { tokenUser: ' ' }, + ))); - it('uses bugfix title when isIssue and isBugfix', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isIssue: true, - isBugfix: true, - currentConfiguration: { results: [new Result({ id: 'a', success: true, executed: true, steps: ['Step'] })] }, - }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith(42, expect.stringContaining('Bugfix Actions')); + expect(comments.listIssueComments).not.toHaveBeenCalled(); + expect(comments.addComment).not.toHaveBeenCalled(); }); - it('uses release title when isPullRequest and release.active', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isPullRequest: true, - release: { active: true }, - currentConfiguration: { results: [new Result({ id: 'a', success: true, executed: true, steps: ['Step'] })] }, - }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith(99, expect.stringContaining('Release Actions')); - }); + it('returns a publication-only failure without replaying or mutating result data', async () => { + const original = recommendation(); + const comments = inMemoryComments(); + comments.listIssueComments.mockRejectedValue(new Error('provider unavailable')); + const context = projectPublishResultContext(source([original])); + original.steps[0] = 'mutated after projection'; - it('uses Automatic Actions when isPullRequest and no release/hotfix/type flags', async () => { - mockAddComment.mockResolvedValue(undefined); - const param = baseParam({ - isPullRequest: true, - release: { active: false }, - hotfix: { active: false }, - currentConfiguration: { results: [new Result({ id: 'a', success: true, executed: true, steps: ['Step'] })] }, - }); - await useCase.invoke(param); - expect(mockAddComment).toHaveBeenCalledWith(99, expect.stringContaining('Automatic Actions')); - }); + const failure = await new PublishResultUseCase(comments).invoke(context); - it('does not call addComment when isPush but issueNumber is 0', async () => { - const param = baseParam({ - isPush: true, - isIssue: false, - isPullRequest: false, - issueNumber: 0, - currentConfiguration: { results: [new Result({ id: 'a', success: true, executed: true, steps: ['Step'] })] }, - }); - await useCase.invoke(param); - expect(mockAddComment).not.toHaveBeenCalled(); + expect(context.results[0].steps).toEqual(['legacy plan wrapper that must never be published']); + expect(failure).toMatchObject({ success: false, executed: true }); + expect(failure?.errors[0]).toMatchObject({ code: 'provider.unavailable' }); }); }); diff --git a/src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts b/src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts new file mode 100644 index 000000000..4171bb1b7 --- /dev/null +++ b/src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts @@ -0,0 +1,69 @@ +import { Result } from '../../../../../data/model/result'; +import { selectSemanticReplyIntents } from '../../../../policies/semantic_result_publication_policy'; +import { reconcileReply } from '../reply_publication_workflow'; + +function intent() { + return selectSemanticReplyIntents({ + locale: 'en-US', target: { kind: 'issue', number: 7 }, correlationId: 'comment:41', botLogin: 'vypbot', + results: [new Result({ + id: 'Comment.Help', success: true, executed: true, + payload: { publication: { kind: 'help', botLogin: 'vypbot' } }, + })], + })[0]; +} + +function ports(initial: Array<{ id: number; body: string | null; user?: { login?: string } }> = []) { + const comments = initial.map(value => ({ ...value })); + let next = Math.max(0, ...comments.map(comment => comment.id)) + 1; + return { + comments, + listIssueComments: jest.fn(async () => comments.map(comment => ({ ...comment }))), + addComment: jest.fn(async (_issue: number, body: string) => { comments.push({ id: next++, body, user: { login: 'vypbot' } }); }), + updateComment: jest.fn(async (_issue: number, id: number, body: string) => { + const comment = comments.find(value => value.id === id); + if (comment) comment.body = body; + }), + }; +} + +const context = () => ({ owner: 'acme', repository: 'widgets', botLogin: 'vypbot', intent: intent() }); + +describe('reply publication workflow', () => { + it('creates a reply once and treats an event replay as unchanged', async () => { + const repository = ports(); + await expect(reconcileReply(context(), repository)).resolves.toMatchObject({ effect: 'created', canonicalCommentId: 1 }); + await expect(reconcileReply(context(), repository)).resolves.toMatchObject({ effect: 'unchanged', canonicalCommentId: 1 }); + expect(repository.addComment).toHaveBeenCalledTimes(1); + }); + + it('ignores a forged marker written by a different user', async () => { + const original = ports(); + await reconcileReply(context(), original); + const forged = ports([{ id: 4, body: original.comments[0].body, user: { login: 'human' } }]); + await reconcileReply(context(), forged); + expect(forged.addComment).toHaveBeenCalledTimes(1); + }); + + it('compacts concurrent bot-owned duplicates and keeps the lowest id', async () => { + const seeded = ports(); + await reconcileReply(context(), seeded); + const body = seeded.comments[0].body; + const repository = ports([ + { id: 9, body, user: { login: 'vypbot' } }, + { id: 3, body, user: { login: 'VypBot' } }, + ]); + + await expect(reconcileReply(context(), repository)).resolves.toEqual({ + effect: 'unchanged', canonicalCommentId: 3, duplicatesCompacted: 1, + }); + expect(repository.comments.find(comment => comment.id === 9)?.body).toContain('/issues/7#issuecomment-3'); + }); + + it('does no provider work without a trusted bot identity', async () => { + const repository = ports(); + await expect(reconcileReply({ ...context(), botLogin: '' }, repository)).resolves.toEqual({ + effect: 'unchanged', duplicatesCompacted: 0, + }); + expect(repository.listIssueComments).not.toHaveBeenCalled(); + }); +}); diff --git a/src/application/usecases/steps/common/__tests__/shared_capability_context_projection.test.ts b/src/application/usecases/steps/common/__tests__/shared_capability_context_projection.test.ts index 27082a270..4a4edee6b 100644 --- a/src/application/usecases/steps/common/__tests__/shared_capability_context_projection.test.ts +++ b/src/application/usecases/steps/common/__tests__/shared_capability_context_projection.test.ts @@ -349,9 +349,8 @@ describe('P2-D shared capability context projections', () => { } as never); results[0].steps[0] = 'Mutated'; - publicationImages.issueAutomaticActions[0] = 'mutated.gif'; expect(context.results[0].steps).toEqual(['Done']); - expect(context.presentation.images.issueAutomaticActions).toEqual(['issue.gif']); + expect(context).toMatchObject({ owner: undefined, repository: undefined, botLogin: '', locale: 'en-US' }); expectDataOnly(context); }); diff --git a/src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts b/src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts new file mode 100644 index 000000000..e2da009ac --- /dev/null +++ b/src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts @@ -0,0 +1,91 @@ +import { reconcileStatusCard } from '../status_card_publication_workflow'; +import { renderSemanticStatus, selectSemanticStatusIntents } from '../../../../policies/semantic_result_publication_policy'; +import { Result } from '../../../../../data/model/result'; + +function intent(summary = 'Current') { + return selectSemanticStatusIntents({ + locale: 'en-US', + results: [new Result({ id: 'CheckProgressUseCase', success: true, executed: true, payload: { issueNumber: 7, progress: 50, summary } })], + })[0]; +} + +function ports(initial: Array<{ id: number; body: string | null; user?: { login?: string } }> = []) { + const comments = initial.map(value => ({ ...value })); + let next = Math.max(0, ...comments.map(comment => comment.id)) + 1; + return { + comments, + listIssueComments: jest.fn(async () => comments.map(comment => ({ ...comment }))), + addComment: jest.fn(async (_issue: number, body: string) => { comments.push({ id: next++, body, user: { login: 'vypbot' } }); }), + updateComment: jest.fn(async (_issue: number, id: number, body: string) => { + const comment = comments.find(value => value.id === id); + if (comment) comment.body = body; + }), + }; +} + +const context = (value = intent()) => ({ owner: 'acme', repository: 'widgets', botLogin: 'vypbot', intent: value }); + +describe('status card publication workflow', () => { + it('creates and discovers one canonical card', async () => { + const repository = ports(); + await expect(reconcileStatusCard(context(), repository)).resolves.toEqual({ effect: 'created', canonicalCommentId: 1, duplicatesCompacted: 0 }); + expect(repository.addComment).toHaveBeenCalledTimes(1); + expect(repository.updateComment).not.toHaveBeenCalled(); + }); + + it('updates a changed canonical card and skips an unchanged one', async () => { + const first = intent('Old'); + const repository = ports([{ id: 3, body: renderSemanticStatus(first), user: { login: 'VypBot' } }]); + const changed = intent('New'); + + await expect(reconcileStatusCard(context(changed), repository)).resolves.toMatchObject({ effect: 'updated', canonicalCommentId: 3 }); + repository.updateComment.mockClear(); + await expect(reconcileStatusCard(context(changed), repository)).resolves.toMatchObject({ effect: 'unchanged' }); + expect(repository.updateComment).not.toHaveBeenCalled(); + }); + + it('ignores human, third-party, malformed, and different-identity comments', async () => { + const value = intent(); + const body = renderSemanticStatus(value); + const repository = ports([ + { id: 1, body, user: { login: 'human' } }, + { id: 2, body: 'not a marker', user: { login: 'vypbot' } }, + { id: 3, body: body.replace('key="work"', 'key="other"'), user: { login: 'vypbot' } }, + ]); + + await reconcileStatusCard(context(value), repository); + + expect(repository.addComment).toHaveBeenCalledTimes(1); + expect(repository.comments).toHaveLength(4); + }); + + it('keeps the lowest bot-owned id and compacts concurrent duplicates', async () => { + const value = intent('Latest'); + const stale = renderSemanticStatus(intent('Stale')); + const repository = ports([ + { id: 9, body: renderSemanticStatus(value), user: { login: 'vypbot' } }, + { id: 4, body: stale, user: { login: 'vypbot' } }, + ]); + + await expect(reconcileStatusCard(context(value), repository)).resolves.toEqual({ + effect: 'updated', canonicalCommentId: 4, duplicatesCompacted: 1, + }); + expect(repository.comments.find(comment => comment.id === 4)?.body).toContain('Latest'); + expect(repository.comments.find(comment => comment.id === 9)?.body).toContain('copilot:publication-duplicate'); + expect(repository.comments.find(comment => comment.id === 9)?.body).toContain('/issues/7#issuecomment-4'); + }); + + it('fails closed without a trusted bot identity', async () => { + const repository = ports(); + await expect(reconcileStatusCard({ ...context(), botLogin: '' }, repository)).resolves.toEqual({ + effect: 'unchanged', duplicatesCompacted: 0, + }); + expect(repository.listIssueComments).not.toHaveBeenCalled(); + }); + + it('tolerates create visibility lag without guessing a comment id', async () => { + const repository = ports(); + repository.addComment.mockImplementation(async () => undefined); + await expect(reconcileStatusCard(context(), repository)).resolves.toEqual({ effect: 'created', duplicatesCompacted: 0 }); + }); +}); diff --git a/src/application/usecases/steps/common/publish_resume_use_case.ts b/src/application/usecases/steps/common/publish_resume_use_case.ts index 47be0eb22..7cca9d0aa 100644 --- a/src/application/usecases/steps/common/publish_resume_use_case.ts +++ b/src/application/usecases/steps/common/publish_resume_use_case.ts @@ -1,6 +1,5 @@ import type { Result } from '../../../../data/model/result'; -import type { BoundIssueNotificationPort } from '../../../ports/issue_lifecycle_ports'; -import type { ApplicationLogReportReaderPort } from '../../../ports/logging_ports'; +import type { BoundIssueCommentPublicationPort } from '../../../ports/issue_lifecycle_ports'; import { logInfo } from '../../../ports/logging_ports'; import { getTaskEmoji } from '../../../../utils/task_emoji'; import { ParamUseCase } from '../../base/param_usecase'; @@ -10,12 +9,11 @@ export class PublishResultUseCase implements ParamUseCase { logInfo(`${getTaskEmoji(this.taskId)} Executing ${this.taskId}.`); - return runPublishResume(param, this.taskId, this.issueNotificationPort, this.logReport); + return runPublishResume(param, this.taskId, this.comments); } } diff --git a/src/application/usecases/steps/common/publish_resume_workflow.ts b/src/application/usecases/steps/common/publish_resume_workflow.ts index 056b77c7e..1ee82d0bd 100644 --- a/src/application/usecases/steps/common/publish_resume_workflow.ts +++ b/src/application/usecases/steps/common/publish_resume_workflow.ts @@ -1,193 +1,148 @@ import { Result } from '../../../../data/model/result'; -import type { BoundIssueNotificationPort } from '../../../ports/issue_lifecycle_ports'; -import type { ApplicationLogReportReaderPort } from '../../../ports/logging_ports'; -import { getRandomElement } from '../../../../utils/list_utils'; +import type { BoundIssueCommentPublicationPort } from '../../../ports/issue_lifecycle_ports'; import { logError, logInfo } from '../../../ports/logging_ports'; -import { - buildDebugLogSection, - hasPublishableContent, - renderResultSections, - resolveResultPublicationIssueNumber, - resolveResultPublicationPresentation, -} from '../../../policies/result_publication_policy'; -import type { - ResultPublicationContext, - ResultPublicationImages, - ResultPublicationRecord, - ResultPublicationTargetInput, -} from '../../../policies/result_publication_contracts'; -import { buildApplicationErrorPresentation } from '../../../policies/application_error_presentation_policy'; +import { selectSemanticReplyIntents, selectSemanticStatusIntents } from '../../../policies/semantic_result_publication_policy'; +import type { PublicationTarget } from '../../../../domain/github_publication'; import { toApplicationError } from '../../../errors/application_error'; +import { createSemanticDigest } from '../../../policies/publication_identity_policy'; +import { reconcileReply } from './reply_publication_workflow'; +import { reconcileStatusCard } from './status_card_publication_workflow'; export interface PublishResultContext { - readonly genericCommentMode: 'publish' | 'omit-feature-owned' | 'omit-metadata-only'; - readonly debug: boolean; - readonly target: ResultPublicationTargetInput; - readonly presentation: ResultPublicationContext; - readonly results: readonly ResultPublicationRecord[]; + readonly owner: string; + readonly repository: string; + readonly botLogin: string; + readonly locale: string; + readonly target?: PublicationTarget; + readonly requestCorrelationId: string; + readonly results: readonly Result[]; } export interface PublishResultContextSource { - readonly debug: boolean; - readonly isSingleAction: boolean; - readonly isIssue: boolean; + readonly owner: string; + readonly repo: string; + readonly tokenUser?: string; + readonly eventName?: string; readonly isPullRequest: boolean; - readonly isPush: boolean; - readonly issueNumber: number; - readonly issueNotBranched: boolean; - readonly isBugfix: boolean; - readonly isFeature: boolean; - readonly isDocs: boolean; - readonly isChore: boolean; - readonly singleAction: { readonly issue: number }; - readonly issue: { readonly number: number }; - readonly pullRequest: { readonly number: number; readonly action: string }; - readonly release: { readonly active: boolean }; - readonly hotfix: { readonly active: boolean }; - readonly images: ResultPublicationImages; - readonly currentConfiguration: { - readonly results: readonly Result[]; - }; + readonly issueNumber?: number; + readonly issue?: { readonly number?: number }; + readonly pullRequest?: { readonly number?: number }; + readonly inputs?: { readonly action?: string; readonly comment?: { readonly id?: number } }; + readonly locale?: { readonly issue: string; readonly pullRequest: string }; + readonly currentConfiguration: { readonly results: readonly Result[] }; } export function projectPublishResultContext(source: PublishResultContextSource): PublishResultContext { - const results = Object.freeze(source.currentConfiguration.results.map(projectResult)); + const target = publicationTarget(source); return Object.freeze({ - genericCommentMode: resolveGenericCommentMode(source, results), - debug: source.debug, - target: Object.freeze({ - isSingleAction: source.isSingleAction, - singleActionIssue: source.singleAction.issue, - isIssue: source.isIssue, - issueNumber: source.issue.number, - isPullRequest: source.isPullRequest, - pullRequestNumber: source.pullRequest.number, - isPush: source.isPush, - pushIssueNumber: source.issueNumber, - }), - presentation: Object.freeze({ - isIssue: source.isIssue, - isPullRequest: source.isPullRequest, - issueNotBranched: source.issueNotBranched, - releaseActive: source.release.active, - hotfixActive: source.hotfix.active, - isBugfix: source.isBugfix, - isFeature: source.isFeature, - isDocs: source.isDocs, - isChore: source.isChore, - images: projectImages(source.images), - }), - results, + owner: source.owner, + repository: source.repo, + botLogin: source.tokenUser?.trim() ?? '', + locale: source.isPullRequest + ? source.locale?.pullRequest ?? 'en-US' + : source.locale?.issue ?? 'en-US', + ...(target ? { target } : {}), + requestCorrelationId: requestCorrelationId(source, target), + results: Object.freeze(source.currentConfiguration.results.map(copyResult)), }); } +/** + * Compatibility boundary for legacy Result producers. Only explicitly mapped + * semantic payloads may reach GitHub; steps, reminders, errors, images, and + * debug logs remain operator evidence in the Job Summary and logs. + */ export async function runPublishResume( param: PublishResultContext, taskId: string, - issueNotificationPort: BoundIssueNotificationPort, - logReport: ApplicationLogReportReaderPort, + comments: BoundIssueCommentPublicationPort, ): Promise { try { - if (param.genericCommentMode !== 'publish') { - logInfo(`Generic result comment omitted: ${param.genericCommentMode}.`); + const semanticContext = { + locale: param.locale, + results: param.results, + ...(param.target ? { target: param.target } : {}), + correlationId: param.requestCorrelationId, + botLogin: param.botLogin, + }; + const replies = selectSemanticReplyIntents(semanticContext); + const statuses = selectSemanticStatusIntents(semanticContext); + if (replies.length === 0 && statuses.length === 0) { + logInfo('Conversation publication omitted: no explicit semantic publication intent.'); return undefined; } - const sections = renderResultSections(param.results); - const debugLogSection = buildDebugLogSection(param.debug, logReport.getAccumulatedLogsAsText()); - if (!hasPublishableContent(sections, debugLogSection)) return undefined; - const issueNumber = resolveResultPublicationIssueNumber(param.target); - if (issueNumber === undefined) return undefined; - await issueNotificationPort.addComment( - issueNumber, - buildResumeComment(param, sections, debugLogSection), - ); + if (!param.botLogin) { + logInfo('Conversation publication omitted: the configured bot identity is unavailable.'); + return undefined; + } + for (const intent of replies) { + const outcome = await reconcileReply({ + owner: param.owner, + repository: param.repository, + botLogin: param.botLogin, + intent, + }, comments); + logInfo(`Semantic ${intent.messageKey} reply ${outcome.effect}; duplicates compacted=${outcome.duplicatesCompacted}.`); + } + for (const intent of statuses) { + const outcome = await reconcileStatusCard({ + owner: param.owner, + repository: param.repository, + botLogin: param.botLogin, + intent, + }, comments); + logInfo(`Semantic ${intent.identity.topic} publication ${outcome.effect}; duplicates compacted=${outcome.duplicatesCompacted}.`); + } return undefined; } catch (error) { - const semanticError = toApplicationError(error, 'provider.unavailable', 'Unable to publish the workflow summary.'); + const semanticError = toApplicationError(error, 'provider.unavailable', 'Unable to publish semantic GitHub status.'); logError(semanticError); - return new Result({ - id: taskId, - success: false, - executed: true, - steps: ['Tried to publish the resume, but there was a problem.'], - errors: [semanticError], - }); + return new Result({ id: taskId, success: false, executed: true, errors: [semanticError] }); } } -function resolveGenericCommentMode( - source: PublishResultContextSource, - results: readonly ResultPublicationRecord[], -): PublishResultContext['genericCommentMode'] { - if (!source.isPullRequest || source.isSingleAction) return 'publish'; - if (source.pullRequest.action === 'edited') return 'omit-metadata-only'; - return results.some((result) => result.id === 'DetectPotentialProblemsUseCase' && result.executed) - ? 'omit-feature-owned' - : 'publish'; +function publicationTarget(source: PublishResultContextSource): PublicationTarget | undefined { + const pullRequestNumber = source.pullRequest?.number; + if (source.isPullRequest && positiveInteger(pullRequestNumber)) { + return Object.freeze({ kind: 'pull-request', number: pullRequestNumber }); + } + const issueNumber = source.issue?.number ?? source.issueNumber; + return positiveInteger(issueNumber) ? Object.freeze({ kind: 'issue', number: issueNumber }) : undefined; } -function buildResumeComment( - param: PublishResultContext, - sections: ReturnType, - debugLogSection: string, -): string { - const presentation = resolveResultPublicationPresentation( - param.presentation, - (images) => getRandomElement([...images]), - ); - const imageMarkdown = shouldRenderImage(param.presentation, presentation.image) - ? `![image](${presentation.image})` - : ''; - return `# ${presentation.title} -${sections.content} -${sections.errors} - -${imageMarkdown} - -${sections.footer} -${debugLogSection} -🚀 Happy coding! - `; +function requestCorrelationId(source: PublishResultContextSource, target: PublicationTarget | undefined): string { + const commentId = source.inputs?.comment?.id; + if (positiveInteger(commentId)) return `comment:${commentId}`; + return `event:${createSemanticDigest({ + eventName: source.eventName ?? 'unknown', + action: source.inputs?.action ?? '', + target: target ?? null, + })}`; } -function shouldRenderImage( - context: ResultPublicationContext, - image: string | undefined, -): image is string { - if (!image) return false; - return context.isIssue - ? context.images.imagesOnIssue - : context.images.imagesOnPullRequest; +function positiveInteger(value: unknown): value is number { + return typeof value === 'number' && Number.isSafeInteger(value) && value > 0; } -function projectResult(result: Result): ResultPublicationRecord { - return Object.freeze({ +function copyResult(result: Result): Result { + return Object.freeze(new Result({ id: result.id, + success: result.success, executed: result.executed, - steps: Object.freeze([...result.steps]), - reminders: Object.freeze([...result.reminders]), - errors: Object.freeze(result.errors.map((error) => Object.freeze(buildApplicationErrorPresentation(error)))), + steps: [...result.steps], + payload: copyPayload(result.payload), + reminders: [...result.reminders], + errors: [...result.errors], stepFormat: result.stepFormat, - }); + })); } -function projectImages(images: ResultPublicationImages): ResultPublicationImages { - return Object.freeze({ - imagesOnIssue: images.imagesOnIssue, - issueAutomaticActions: Object.freeze([...images.issueAutomaticActions]), - issueFeatureGifs: Object.freeze([...images.issueFeatureGifs]), - issueBugfixGifs: Object.freeze([...images.issueBugfixGifs]), - issueReleaseGifs: Object.freeze([...images.issueReleaseGifs]), - issueHotfixGifs: Object.freeze([...images.issueHotfixGifs]), - issueDocsGifs: Object.freeze([...images.issueDocsGifs]), - issueChoreGifs: Object.freeze([...images.issueChoreGifs]), - imagesOnPullRequest: images.imagesOnPullRequest, - pullRequestAutomaticActions: Object.freeze([...images.pullRequestAutomaticActions]), - pullRequestFeatureGifs: Object.freeze([...images.pullRequestFeatureGifs]), - pullRequestBugfixGifs: Object.freeze([...images.pullRequestBugfixGifs]), - pullRequestReleaseGifs: Object.freeze([...images.pullRequestReleaseGifs]), - pullRequestHotfixGifs: Object.freeze([...images.pullRequestHotfixGifs]), - pullRequestDocsGifs: Object.freeze([...images.pullRequestDocsGifs]), - pullRequestChoreGifs: Object.freeze([...images.pullRequestChoreGifs]), - }); +function copyPayload(payload: unknown): unknown { + if (Array.isArray(payload)) return Object.freeze(payload.map(copyPayload)); + if (payload && typeof payload === 'object') { + return Object.freeze(Object.fromEntries( + Object.entries(payload as Record).map(([key, value]) => [key, copyPayload(value)]), + )); + } + return payload; } diff --git a/src/application/usecases/steps/common/reply_publication_workflow.ts b/src/application/usecases/steps/common/reply_publication_workflow.ts new file mode 100644 index 000000000..4001da733 --- /dev/null +++ b/src/application/usecases/steps/common/reply_publication_workflow.ts @@ -0,0 +1,69 @@ +import { publicationTargetToken } from '../../../../domain/github_publication'; +import { githubUsersMatch } from '../../../../domain/github_user_policy'; +import type { BoundIssueCommentPublicationPort, IssueCommentPublicationTarget } from '../../../ports/issue_lifecycle_ports'; +import { buildDuplicateMarker, parsePublicationReplyMarker } from '../../../policies/publication_identity_policy'; +import { resolveStaticPublicationCatalog } from '../../../policies/publication_message_catalog'; +import { renderSemanticReply, type SemanticReplyIntent } from '../../../policies/semantic_result_publication_policy'; + +export interface ReplyPublicationContext { + readonly owner: string; + readonly repository: string; + readonly botLogin: string; + readonly intent: SemanticReplyIntent; +} + +export interface ReplyPublicationOutcome { + readonly effect: 'created' | 'unchanged'; + readonly canonicalCommentId?: number; + readonly duplicatesCompacted: number; +} + +/** Publishes an explicit reply at most once for a trusted request correlation. */ +export async function reconcileReply( + context: ReplyPublicationContext, + comments: BoundIssueCommentPublicationPort, +): Promise { + if (!context.botLogin.trim()) return Object.freeze({ effect: 'unchanged', duplicatesCompacted: 0 }); + const target = context.intent.target; + let owned = matchingReplies(await comments.listIssueComments(target.number), context); + let effect: ReplyPublicationOutcome['effect'] = 'unchanged'; + if (owned.length === 0) { + await comments.addComment(target.number, renderSemanticReply(context.intent)); + effect = 'created'; + owned = matchingReplies(await comments.listIssueComments(target.number), context); + } + if (owned.length === 0) return Object.freeze({ effect, duplicatesCompacted: 0 }); + + const [canonical, ...duplicates] = owned.sort((left, right) => left.id - right.id); + for (const duplicate of duplicates) { + await comments.updateComment(target.number, duplicate.id, duplicatePointer(context, canonical.id)); + } + return Object.freeze({ effect, canonicalCommentId: canonical.id, duplicatesCompacted: duplicates.length }); +} + +function matchingReplies( + comments: readonly IssueCommentPublicationTarget[], + context: ReplyPublicationContext, +): IssueCommentPublicationTarget[] { + const expectedTarget = publicationTargetToken(context.intent.target); + return comments.filter(comment => { + const marker = parsePublicationReplyMarker(comment.body); + return marker?.target === expectedTarget + && marker.correlationId === context.intent.correlationId + && marker.messageKey === context.intent.messageKey + && githubUsersMatch(comment.user?.login ?? '', context.botLogin); + }); +} + +function duplicatePointer(context: ReplyPublicationContext, canonicalCommentId: number): string { + const messages = resolveStaticPublicationCatalog(context.intent.locale).catalog; + const targetPath = context.intent.target.kind === 'pull-request' ? 'pull' : 'issues'; + const url = `https://github.com/${encodeURIComponent(context.owner)}/${encodeURIComponent(context.repository)}/${targetPath}/${context.intent.target.number}#issuecomment-${canonicalCommentId}`; + return [ + buildDuplicateMarker(canonicalCommentId), + '', + messages.duplicateReply, + '', + `[${messages.viewOriginalResponse}](${url}).`, + ].join('\n'); +} diff --git a/src/application/usecases/steps/common/status_card_publication_workflow.ts b/src/application/usecases/steps/common/status_card_publication_workflow.ts new file mode 100644 index 000000000..1ed269b4f --- /dev/null +++ b/src/application/usecases/steps/common/status_card_publication_workflow.ts @@ -0,0 +1,78 @@ +import type { PublicationIdentity } from '../../../../domain/github_publication'; +import { publicationIdentityEquals } from '../../../../domain/github_publication'; +import { githubUsersMatch } from '../../../../domain/github_user_policy'; +import type { BoundIssueCommentPublicationPort, IssueCommentPublicationTarget } from '../../../ports/issue_lifecycle_ports'; +import { buildDuplicateMarker, parsePublicationMarker } from '../../../policies/publication_identity_policy'; +import { resolveStaticPublicationCatalog } from '../../../policies/publication_message_catalog'; +import { renderSemanticStatus, type SemanticStatusIntent } from '../../../policies/semantic_result_publication_policy'; + +export interface StatusCardPublicationContext { + readonly owner: string; + readonly repository: string; + readonly botLogin: string; + readonly intent: SemanticStatusIntent; +} + +export interface StatusCardPublicationOutcome { + readonly effect: 'created' | 'updated' | 'unchanged'; + readonly canonicalCommentId?: number; + readonly duplicatesCompacted: number; +} + +export async function reconcileStatusCard( + context: StatusCardPublicationContext, + comments: BoundIssueCommentPublicationPort, +): Promise { + if (!context.botLogin.trim()) return Object.freeze({ effect: 'unchanged', duplicatesCompacted: 0 }); + const target = context.intent.identity.target; + const rendered = renderSemanticStatus(context.intent); + let owned = ownedCards(await comments.listIssueComments(target.number), context.intent.identity, context.botLogin); + let effect: StatusCardPublicationOutcome['effect'] = 'unchanged'; + if (owned.length === 0) { + await comments.addComment(target.number, rendered); + effect = 'created'; + owned = ownedCards(await comments.listIssueComments(target.number), context.intent.identity, context.botLogin); + } + if (owned.length === 0) return Object.freeze({ effect, duplicatesCompacted: 0 }); + + const [canonical, ...duplicates] = owned.sort((left, right) => left.id - right.id); + const canonicalMarker = parsePublicationMarker(canonical.body); + if (canonicalMarker?.digest !== context.intent.digest) { + await comments.updateComment(target.number, canonical.id, rendered); + effect = effect === 'created' ? 'created' : 'updated'; + } + for (const duplicate of duplicates) { + await comments.updateComment(target.number, duplicate.id, duplicatePointer(context, canonical.id)); + } + return Object.freeze({ + effect, + canonicalCommentId: canonical.id, + duplicatesCompacted: duplicates.length, + }); +} + +function ownedCards( + comments: readonly IssueCommentPublicationTarget[], + identity: PublicationIdentity, + botLogin: string, +): IssueCommentPublicationTarget[] { + return comments.filter(comment => { + const marker = parsePublicationMarker(comment.body); + return marker !== undefined + && publicationIdentityEquals(marker.identity, identity) + && githubUsersMatch(comment.user?.login ?? '', botLogin); + }); +} + +function duplicatePointer(context: StatusCardPublicationContext, canonicalCommentId: number): string { + const messages = resolveStaticPublicationCatalog(context.intent.locale).catalog; + const targetPath = context.intent.identity.target.kind === 'pull-request' ? 'pull' : 'issues'; + const url = `https://github.com/${encodeURIComponent(context.owner)}/${encodeURIComponent(context.repository)}/${targetPath}/${context.intent.identity.target.number}#issuecomment-${canonicalCommentId}`; + return [ + buildDuplicateMarker(canonicalCommentId), + '', + messages.supersededStatus, + '', + `[${messages.viewCurrentStatus}](${url}).`, + ].join('\n'); +} diff --git a/src/application/usecases/steps/issue/__tests__/close_issue_after_merging_use_case.test.ts b/src/application/usecases/steps/issue/__tests__/close_issue_after_merging_use_case.test.ts index e6b82288e..34ba22f12 100644 --- a/src/application/usecases/steps/issue/__tests__/close_issue_after_merging_use_case.test.ts +++ b/src/application/usecases/steps/issue/__tests__/close_issue_after_merging_use_case.test.ts @@ -7,7 +7,6 @@ jest.mock('../../../../../utils/logger', () => ({ })); const mockCloseIssue = jest.fn(); -const mockAddComment = jest.fn(); function baseParam() { return { @@ -20,14 +19,12 @@ describe('CloseIssueAfterMergingUseCase', () => { let useCase: CloseIssueAfterMergingUseCase; beforeEach(() => { - useCase = new CloseIssueAfterMergingUseCase({ closeIssue: mockCloseIssue, addComment: mockAddComment }); + useCase = new CloseIssueAfterMergingUseCase({ closeIssue: mockCloseIssue }); mockCloseIssue.mockReset(); - mockAddComment.mockReset(); }); - it('closes issue and adds comment when closeIssue returns true', async () => { + it('closes the issue using native state without adding a timeline comment', async () => { mockCloseIssue.mockResolvedValue(true); - mockAddComment.mockResolvedValue(undefined); const param = baseParam(); const results = await useCase.invoke(param); @@ -37,10 +34,6 @@ describe('CloseIssueAfterMergingUseCase', () => { expect(results[0].executed).toBe(true); expect(results[0].steps?.some((s) => s.includes('42') && s.includes('closed'))).toBe(true); expect(mockCloseIssue).toHaveBeenCalledWith(42); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringContaining('closed after merging #10'), - ); }); it('returns success executed false when closeIssue returns false', async () => { @@ -49,7 +42,6 @@ describe('CloseIssueAfterMergingUseCase', () => { const results = await useCase.invoke(param); expect(results[0].success).toBe(true); expect(results[0].executed).toBe(false); - expect(mockAddComment).not.toHaveBeenCalled(); }); it('does not call GitHub when a pull request has no linked issue', async () => { @@ -57,7 +49,6 @@ describe('CloseIssueAfterMergingUseCase', () => { expect(results[0]).toMatchObject({ success: true, executed: false }); expect(mockCloseIssue).not.toHaveBeenCalled(); - expect(mockAddComment).not.toHaveBeenCalled(); }); it('returns failure when closeIssue throws', async () => { diff --git a/src/application/usecases/steps/issue/__tests__/close_not_allowed_issue_use_case.test.ts b/src/application/usecases/steps/issue/__tests__/close_not_allowed_issue_use_case.test.ts index 8bda49590..81e411d81 100644 --- a/src/application/usecases/steps/issue/__tests__/close_not_allowed_issue_use_case.test.ts +++ b/src/application/usecases/steps/issue/__tests__/close_not_allowed_issue_use_case.test.ts @@ -7,7 +7,6 @@ jest.mock('../../../../../utils/logger', () => ({ })); const mockCloseIssue = jest.fn(); -const mockAddComment = jest.fn(); function baseParam() { return { @@ -19,14 +18,12 @@ describe('CloseNotAllowedIssueUseCase', () => { let useCase: CloseNotAllowedIssueUseCase; beforeEach(() => { - useCase = new CloseNotAllowedIssueUseCase({ closeIssue: mockCloseIssue, addComment: mockAddComment }); + useCase = new CloseNotAllowedIssueUseCase({ closeIssue: mockCloseIssue }); mockCloseIssue.mockReset(); - mockAddComment.mockReset(); }); - it('returns success executed true and calls addComment when closeIssue returns true', async () => { + it('returns a semantic terminal explanation when closeIssue returns true', async () => { mockCloseIssue.mockResolvedValue(true); - mockAddComment.mockResolvedValue(undefined); const param = baseParam(); const results = await useCase.invoke(param); @@ -36,10 +33,7 @@ describe('CloseNotAllowedIssueUseCase', () => { expect(results[0].executed).toBe(true); expect(results[0].steps?.some((s) => s.includes('42') && s.includes('closed'))).toBe(true); expect(mockCloseIssue).toHaveBeenCalledWith(42); - expect(mockAddComment).toHaveBeenCalledWith( - 42, - expect.stringContaining('closed because the author is not a member'), - ); + expect(results[0].payload).toEqual({ publication: { kind: 'access-policy' } }); }); it('returns success executed false when closeIssue returns false', async () => { @@ -50,7 +44,6 @@ describe('CloseNotAllowedIssueUseCase', () => { expect(results[0].success).toBe(true); expect(results[0].executed).toBe(false); - expect(mockAddComment).not.toHaveBeenCalled(); }); it('returns failure when closeIssue throws', async () => { diff --git a/src/application/usecases/steps/issue/close_issue_after_merging_use_case.ts b/src/application/usecases/steps/issue/close_issue_after_merging_use_case.ts index ef710275f..409ea474d 100644 --- a/src/application/usecases/steps/issue/close_issue_after_merging_use_case.ts +++ b/src/application/usecases/steps/issue/close_issue_after_merging_use_case.ts @@ -1,5 +1,5 @@ import { Result } from "../../../../data/model/result"; -import type { BoundIssueClosurePort } from "../../../../application/ports/issue_lifecycle_ports"; +import type { BoundIssueStatePort } from "../../../../application/ports/issue_lifecycle_ports"; import { logDebugInfo, logError, logInfo } from "../../../ports/logging_ports"; import { getTaskEmoji } from "../../../../utils/task_emoji"; import { ParamUseCase } from "../../base/param_usecase"; @@ -9,7 +9,7 @@ import type { CloseIssueAfterMergeContext } from '../../issue_workflow_context'; export class CloseIssueAfterMergingUseCase implements ParamUseCase { taskId: string = 'CloseIssueAfterMergingUseCase'; - constructor(private readonly issueRepository: BoundIssueClosurePort) {} + constructor(private readonly issueRepository: BoundIssueStatePort) {} async invoke(param: CloseIssueAfterMergeContext): Promise { logInfo(`${getTaskEmoji(this.taskId)} Executing ${this.taskId}.`) @@ -28,10 +28,6 @@ export class CloseIssueAfterMergingUseCase implements ParamUseCase { taskId: string = 'CloseNotAllowedIssueUseCase'; - constructor(private readonly issueRepository: BoundIssueClosurePort) {} + constructor(private readonly issueRepository: BoundIssueStatePort) {} async invoke(param: IssueNumberContext): Promise { logInfo(`${getTaskEmoji(this.taskId)} Executing ${this.taskId}.`) @@ -18,19 +18,14 @@ export class CloseNotAllowedIssueUseCase implements ParamUseCase { + const path = join(directory, name); + if (statSync(path).isDirectory()) return name === '__tests__' ? [] : productionFiles(path); + return path.endsWith('.ts') ? [path] : []; + }); +} + +function inventory(): BaselineEntry[] { + return productionFiles(applicationRoot).flatMap(path => { + const source = readFileSync(path, 'utf8'); + const counts = Object.fromEntries(methods.map(method => [ + method, + [...source.matchAll(new RegExp(`\\.${method}\\s*\\(`, 'gu'))].length, + ])); + if (Object.values(counts).every(count => count === 0)) return []; + return [{ + file: relative(root, path), + ...Object.fromEntries(Object.entries(counts).filter(([, count]) => count > 0)), + reason: baseline.entries.find(entry => entry.file === relative(root, path))?.reason ?? '', + } as BaselineEntry]; + }).sort((left, right) => left.file.localeCompare(right.file)); +} + +describe('GitHub conversation publication boundaries', () => { + it('matches the exact reviewed application mutation inventory', () => { + expect(baseline.entries.every(entry => entry.reason.trim().length >= 24)).toBe(true); + expect(baseline.entries.every(entry => existsSync(join(root, entry.file)))).toBe(true); + expect(inventory()).toEqual([...baseline.entries].sort((left, right) => left.file.localeCompare(right.file))); + }); + + it('contains none of the retired generic conversation chrome', () => { + const retired = [ + /Automatic Actions/u, + /Feature Actions/u, + /Bugfix Actions/u, + /Documentation Actions/u, + /Chore Actions/u, + /Happy coding/u, + /Debug log<\/summary>/u, + /Made with .*Marketplace/u, + ]; + const violations = productionFiles(applicationRoot).flatMap(path => { + const source = readFileSync(path, 'utf8'); + return retired.some(pattern => pattern.test(source)) ? [relative(root, path)] : []; + }); + expect(violations).toEqual([]); + }); +}); diff --git a/src/architecture/github_publication_mutation_baseline.json b/src/architecture/github_publication_mutation_baseline.json new file mode 100644 index 000000000..fd54ada77 --- /dev/null +++ b/src/architecture/github_publication_mutation_baseline.json @@ -0,0 +1,15 @@ +{ + "entries": [ + { "file": "src/application/usecases/actions/close_inactive_issues_workflow.ts", "addComment": 1, "reason": "Publishes the terminal inactivity policy after a successful native close." }, + { "file": "src/application/usecases/actions/observe_branch_sync_use_case.ts", "addComment": 1, "updateComment": 2, "reason": "Reconciles the feature-owned branch synchronization card." }, + { "file": "src/application/usecases/actions/publish_issue_comment_workflow.ts", "addComment": 1, "updateComment": 1, "reason": "Executes the explicitly requested caller-supplied comment mutation." }, + { "file": "src/application/usecases/steps/commit/bugbot/publish_issue_finding_comment.ts", "addComment": 1, "updateComment": 1, "reason": "Creates or updates one Bugbot issue finding." }, + { "file": "src/application/usecases/steps/commit/bugbot/publish_overflow_comment.ts", "addComment": 1, "reason": "Publishes the bounded Bugbot overflow record." }, + { "file": "src/application/usecases/steps/commit/bugbot/resolve_issue_finding.ts", "updateComment": 1, "reason": "Transitions an existing Bugbot finding to its resolved state." }, + { "file": "src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts", "addComment": 1, "updateComment": 2, "updatePullRequestReview": 1, "reason": "Reconciles the Bugbot aggregate card, duplicates, and historical review state." }, + { "file": "src/application/usecases/steps/common/reply_publication_workflow.ts", "addComment": 1, "updateComment": 1, "reason": "Publishes a correlated explicit reply and compacts concurrent duplicates." }, + { "file": "src/application/usecases/steps/common/status_card_publication_workflow.ts", "addComment": 1, "updateComment": 2, "reason": "Creates or reconciles one canonical semantic status card." }, + { "file": "src/application/usecases/steps/common/think_answer_workflow.ts", "addComment": 1, "reason": "Publishes the single direct response to an addressed natural-language request." }, + { "file": "src/application/usecases/steps/issue/answer_issue_help_workflow.ts", "addComment": 1, "reason": "Publishes the single direct answer for a question or help issue." } + ] +} diff --git a/src/data/repository/issue/__tests__/issue_content_repository.test.ts b/src/data/repository/issue/__tests__/issue_content_repository.test.ts index 93235a5c4..c0da24e77 100644 --- a/src/data/repository/issue/__tests__/issue_content_repository.test.ts +++ b/src/data/repository/issue/__tests__/issue_content_repository.test.ts @@ -54,15 +54,15 @@ describe('IssueContentRepository', () => { await expect(repository.getDescription('owner', 'repo', 7, 'token')).rejects.toThrow('Not Found'); }); - it('preserves the comment watermark contract', async () => { + it('publishes exactly the semantic body without a visible branding footer', async () => { mockCreateComment.mockResolvedValue(undefined); mockUpdateComment.mockResolvedValue(undefined); await repository.addComment('owner', 'repo', 7, 'comment', 'token'); await repository.updateComment('owner', 'repo', 7, 12, 'updated', 'token'); - expect(mockCreateComment).toHaveBeenCalledWith(expect.objectContaining({ issue_number: 7 })); - expect(mockUpdateComment).toHaveBeenCalledWith(expect.objectContaining({ comment_id: 12 })); + expect(mockCreateComment).toHaveBeenCalledWith(expect.objectContaining({ issue_number: 7, body: 'comment' })); + expect(mockUpdateComment).toHaveBeenCalledWith(expect.objectContaining({ comment_id: 12, body: 'updated' })); }); it.each(['', ' ', '\n\n'])('does not publish an empty comment body: %j', async (comment) => { diff --git a/src/data/repository/issue/issue_content_repository.ts b/src/data/repository/issue/issue_content_repository.ts index 517bee18d..6aa5662b4 100644 --- a/src/data/repository/issue/issue_content_repository.ts +++ b/src/data/repository/issue/issue_content_repository.ts @@ -1,4 +1,3 @@ -import { getCommentWatermark } from "../../../utils/comment_watermark"; import { hasVisibleCommentContent } from '../../../domain/comment_content_policy'; import { logDebugInfo, logError } from "../../../utils/logger"; import type { GithubClientPort } from "../../../infrastructure/github/ports/github_client_provider_port"; @@ -90,15 +89,13 @@ export class IssueContentRepository { return; } - const watermark = getCommentWatermark( - options?.commitSha ? { commitSha: options.commitSha, owner, repo: repository } : undefined, - ); + void options; const octokit = this.githubClient.getClient(token); await octokit.rest.issues.createComment({ owner, repo: repository, issue_number: issueNumber, - body: `${comment}\n\n${watermark}`, + body: comment, }); logDebugInfo(`Comment added to Issue ${issueNumber}.`); }; @@ -117,15 +114,13 @@ export class IssueContentRepository { return; } - const watermark = getCommentWatermark( - options?.commitSha ? { commitSha: options.commitSha, owner, repo: repository } : undefined, - ); + void options; const octokit = this.githubClient.getClient(token); await octokit.rest.issues.updateComment({ owner, repo: repository, comment_id: commentId, - body: `${comment}\n\n${watermark}`, + body: comment, }); logDebugInfo(`Comment ${commentId} updated in Issue ${issueNumber}.`); }; diff --git a/src/domain/__tests__/github_publication.test.ts b/src/domain/__tests__/github_publication.test.ts new file mode 100644 index 000000000..ce7e4c20f --- /dev/null +++ b/src/domain/__tests__/github_publication.test.ts @@ -0,0 +1,39 @@ +import { + publicationIdentityEquals, + publicationTargetToken, + type PublicationIntent, + type PublicationIdentity, +} from '../github_publication'; + +const identity: PublicationIdentity = { + topic: 'progress', target: { kind: 'issue', number: 7 }, key: 'work', +}; + +describe('GitHub publication domain', () => { + it('compares all semantic identity components', () => { + expect(publicationIdentityEquals(identity, { ...identity })).toBe(true); + expect(publicationIdentityEquals(identity, { ...identity, topic: 'plan' })).toBe(false); + expect(publicationIdentityEquals(identity, { ...identity, target: { kind: 'pull-request', number: 7 } })).toBe(false); + expect(publicationIdentityEquals(identity, { ...identity, target: { kind: 'issue', number: 8 } })).toBe(false); + expect(publicationIdentityEquals(identity, { ...identity, key: 'other' })).toBe(false); + }); + + it.each([ + [{ kind: 'issue', number: 12 }, 'issue:12'], + [{ kind: 'pull-request', number: 13 }, 'pr:13'], + ] as const)('renders a stable provider-independent target token', (target, expected) => { + expect(publicationTargetToken(target)).toBe(expected); + }); + + it('keeps the publication union closed and discriminated', () => { + const intents: PublicationIntent[] = [ + { kind: 'none', reason: 'routine' }, + { kind: 'reply', target: identity.target, correlationId: 'request-1', messageKey: 'done', locale: 'en-US', digest: '12345678', projection: {} }, + { kind: 'status', identity, sourceVersion: 'head:abc', digest: '12345678', locale: 'en-US', projection: {} }, + { kind: 'transition', identity, fingerprint: '12345678', messageKey: 'action', values: {} }, + { kind: 'inline-finding', identity: 'finding-1', path: 'src/a.ts', line: 1, severity: 'high', title: 'Unsafe input', evidence: 'Untrusted value reaches SQL.' }, + ]; + + expect(intents.map(intent => intent.kind)).toEqual(['none', 'reply', 'status', 'transition', 'inline-finding']); + }); +}); diff --git a/src/domain/github_publication.ts b/src/domain/github_publication.ts new file mode 100644 index 000000000..ddf443e7b --- /dev/null +++ b/src/domain/github_publication.ts @@ -0,0 +1,92 @@ +export const PUBLICATION_TOPICS = [ + 'plan', + 'progress', + 'branch-sync', + 'bugbot', + 'release', + 'inactivity', + 'access-policy', +] as const; + +export type PublicationTopic = typeof PUBLICATION_TOPICS[number]; +export type PublicationTargetKind = 'issue' | 'pull-request'; + +export interface PublicationTarget { + readonly kind: PublicationTargetKind; + readonly number: number; +} + +export interface PublicationIdentity { + readonly topic: PublicationTopic; + readonly target: PublicationTarget; + readonly key: string; +} + +export type NoPublicationReason = + | 'routine' + | 'no-op' + | 'duplicate' + | 'stale-source' + | 'feature-owned' + | 'missing-target' + | 'missing-owner'; + +export interface NoPublicationIntent { + readonly kind: 'none'; + readonly reason: NoPublicationReason; +} + +export interface ReplyPublicationIntent { + readonly kind: 'reply'; + readonly target: PublicationTarget; + readonly correlationId: string; + readonly messageKey: string; + readonly locale: string; + readonly digest: string; + readonly projection: Readonly; +} + +export interface StatusPublicationIntent { + readonly kind: 'status'; + readonly identity: PublicationIdentity; + readonly sourceVersion: string; + readonly digest: string; + readonly locale: string; + readonly projection: Readonly; +} + +export interface TransitionPublicationIntent { + readonly kind: 'transition'; + readonly identity: PublicationIdentity; + readonly fingerprint: string; + readonly messageKey: string; + readonly values: Readonly>; +} + +export interface InlineFindingPublicationIntent { + readonly kind: 'inline-finding'; + readonly identity: string; + readonly path: string; + readonly line: number; + readonly severity: 'info' | 'low' | 'medium' | 'high'; + readonly title: string; + readonly evidence: string; +} + +export type PublicationIntent = + | NoPublicationIntent + | ReplyPublicationIntent + | StatusPublicationIntent + | TransitionPublicationIntent + | InlineFindingPublicationIntent; + +export function publicationIdentityEquals(left: PublicationIdentity, right: PublicationIdentity): boolean { + return left.topic === right.topic + && left.target.kind === right.target.kind + && left.target.number === right.target.number + && left.key === right.key; +} + +export function publicationTargetToken(target: PublicationTarget): string { + return `${target.kind === 'pull-request' ? 'pr' : 'issue'}:${target.number}`; +} diff --git a/src/infrastructure/composition/__tests__/lifecycle_capability_port_binding.test.ts b/src/infrastructure/composition/__tests__/lifecycle_capability_port_binding.test.ts index a35d818c2..b3b5eb22e 100644 --- a/src/infrastructure/composition/__tests__/lifecycle_capability_port_binding.test.ts +++ b/src/infrastructure/composition/__tests__/lifecycle_capability_port_binding.test.ts @@ -4,6 +4,7 @@ import { bindBranchWorkflow, bindIssueAssignee, bindIssueClosure, + bindIssueState, bindIssueLabels, bindIssueTypeAssignment, bindLinkedBranchCommand, @@ -55,8 +56,10 @@ describe('lifecycle capability repository bindings', () => { const setLabels = jest.fn().mockResolvedValue(undefined); const getPullRequestHeadSha = jest.fn().mockResolvedValue('sha-1'); const closure = bindIssueClosure({ closeIssue, addComment }, binding); + const state = bindIssueState({ closeIssue }, binding); await closure.closeIssue(7); await closure.addComment(7, 'done'); + await state.closeIssue(8); const selected = { name: 'Feature', description: 'Feature issue', color: 'GREEN' }; await bindIssueTypeAssignment({ setIssueType }, binding).setIssueType(7, selected); const labels = bindIssueLabels({ getLabels, setLabels }, binding); @@ -65,6 +68,7 @@ describe('lifecycle capability repository bindings', () => { const head = bindPullRequestHeadSha({ getPullRequestHeadSha }, binding); await expect(head.getPullRequestHeadSha(9)).resolves.toBe('sha-1'); expect(closeIssue).toHaveBeenCalledWith('acme', 'demo', 7, 'secret'); + expect(closeIssue).toHaveBeenCalledWith('acme', 'demo', 8, 'secret'); expect(addComment).toHaveBeenCalledWith('acme', 'demo', 7, 'done', 'secret'); expect(setIssueType).toHaveBeenCalledWith('acme', 'demo', 7, selected, 'secret'); expect(setLabels).toHaveBeenCalledWith('acme', 'demo', 8, ['size: S'], 'secret'); diff --git a/src/infrastructure/composition/__tests__/push_single_action_capability_port_binding.test.ts b/src/infrastructure/composition/__tests__/push_single_action_capability_port_binding.test.ts index 5ae6bff3c..00160e963 100644 --- a/src/infrastructure/composition/__tests__/push_single_action_capability_port_binding.test.ts +++ b/src/infrastructure/composition/__tests__/push_single_action_capability_port_binding.test.ts @@ -17,7 +17,7 @@ import { bindIssueCommentPublication, bindIssueInactivityQuery, bindIssueProgress, - bindIssuePushNotification, + bindIssueReopen, bindIssueTypes, bindInitialLabels, bindManagedPullRequests, @@ -268,12 +268,12 @@ describe('push and single-action capability binding', () => { }); it('forwards every remaining issue, branch-query, and synchronization operation', async () => { - const issuePushPort = { openIssue: jest.fn(), addComment: jest.fn() }; + const issuePushPort = { openIssue: jest.fn() }; const inactivityPort = { listOpenIssuesByLabel: jest.fn(), getOpenIssue: jest.fn() }; const dependencyPort = { listOpenDependencies: jest.fn(), resolveTarget: jest.fn() }; const notificationPort = { listIssueComments: jest.fn(), addComment: jest.fn(), updateComment: jest.fn() }; const defaultBranch = bindRepositoryDefaultBranch({ getDefaultBranch: jest.fn() } as never, binding); - const issuePush = bindIssuePushNotification(issuePushPort as never, binding); + const issuePush = bindIssueReopen(issuePushPort as never, binding); const branches = bindBranchListQuery({ getListOfBranches: jest.fn() } as never, binding); const pullRequests = bindPullRequestBranchQuery({ getOpenPullRequestNumbersByHeadBranch: jest.fn() } as never, binding); const progress = bindIssueProgress({ setProgressLabel: jest.fn() } as never, binding); @@ -285,7 +285,6 @@ describe('push and single-action capability binding', () => { await defaultBranch.getDefaultBranch(); await issuePush.openIssue(42); - await issuePush.addComment(42, 'body'); await branches.getListOfBranches(); await pullRequests.getOpenPullRequestNumbersByHeadBranch('feature/42'); await progress.setProgressLabel(42, 75); diff --git a/src/infrastructure/composition/issue_use_case_composition_root.ts b/src/infrastructure/composition/issue_use_case_composition_root.ts index ffcc2f588..60dd62d8d 100644 --- a/src/infrastructure/composition/issue_use_case_composition_root.ts +++ b/src/infrastructure/composition/issue_use_case_composition_root.ts @@ -56,7 +56,7 @@ import { bindBranchLifecycle, bindBranchWorkflow, bindIssueAssignee, - bindIssueClosure, + bindIssueState, bindIssueTypeAssignment, bindLinkedBranchCommand, bindOrganizationMemberSelection, @@ -102,7 +102,6 @@ export function createIssueUseCaseCompositionRoot(binding: RepositoryCredentialB ); const boundProjectBoard = bindProjectBoardCommands(projectBoard.command, binding); const boundBranchLifecycle = bindBranchLifecycle(branchLifecycle, binding); - const boundIssueClosure = bindIssueClosure(issueClosure, binding); const boundIssueAssignee = bindIssueAssignee(issueAssignee, binding); const boundOrganizationMembers = bindOrganizationMemberSelection(organizationMembers, binding); const boundLinkedBranch = bindLinkedBranchCommand(linkedBranch, binding); @@ -110,7 +109,7 @@ export function createIssueUseCaseCompositionRoot(binding: RepositoryCredentialB const workflowSteps = { checkPermissions: new CheckPermissionsUseCase(bindOrganizationMembers(organizationMembers, binding)), - closeNotAllowedIssue: new CloseNotAllowedIssueUseCase(boundIssueClosure), + closeNotAllowedIssue: new CloseNotAllowedIssueUseCase(bindIssueState(issueClosure, binding)), removeIssueBranches: new RemoveIssueBranchesUseCase(boundBranchLifecycle), assignMemberToIssue: new AssignMemberToIssueUseCase( boundIssueAssignee, diff --git a/src/infrastructure/composition/lifecycle_capability_port_binding.ts b/src/infrastructure/composition/lifecycle_capability_port_binding.ts index f3f277a0f..2c5002704 100644 --- a/src/infrastructure/composition/lifecycle_capability_port_binding.ts +++ b/src/infrastructure/composition/lifecycle_capability_port_binding.ts @@ -16,6 +16,7 @@ import type { } from '../../application/ports/branch_workflow_ports'; import type { BoundIssueClosurePort, + BoundIssueStatePort, IssueClosurePort, } from '../../application/ports/issue_lifecycle_ports'; import type { @@ -144,6 +145,20 @@ export function bindIssueClosure( } satisfies BoundIssueClosurePort); } +export function bindIssueState( + port: Pick, + binding: RepositoryCredentialBinding, +): BoundIssueStatePort { + return Object.freeze({ + closeIssue: (issueNumber) => port.closeIssue( + binding.owner, + binding.repository, + issueNumber, + binding.token, + ), + } satisfies BoundIssueStatePort); +} + export function bindIssueTypeAssignment( port: IssueTypeAssignmentPort, binding: RepositoryCredentialBinding, diff --git a/src/infrastructure/composition/main_run_route_composition_root.ts b/src/infrastructure/composition/main_run_route_composition_root.ts index 001eb5c64..bee13cd97 100644 --- a/src/infrastructure/composition/main_run_route_composition_root.ts +++ b/src/infrastructure/composition/main_run_route_composition_root.ts @@ -93,7 +93,7 @@ import { bindDeploymentState, bindDeploymentTargetRules, bindIssueCommentPublication, - bindIssuePushNotification, + bindIssueReopen, bindManagedPullRequests, bindPullRequestBranchQuery, bindRepositoryRelease, @@ -276,7 +276,7 @@ export function createCommitUseCaseCompositionRoot( binding: BugbotScmBinding, ): CommitUseCase { return new CommitUseCase( - new NotifyNewCommitOnIssueUseCase(bindIssuePushNotification(createIssueNotificationRepository(), binding)), + new NotifyNewCommitOnIssueUseCase(bindIssueReopen(createIssueNotificationRepository(), binding)), new CheckChangesIssueSizeUseCase( bindProjectBoardCommands(projectBoardCommandPort, binding), bindIssueLabels(createIssueLabelRepository(), binding), diff --git a/src/infrastructure/composition/push_single_action_capability_port_binding.ts b/src/infrastructure/composition/push_single_action_capability_port_binding.ts index 39791fd39..2b48bffa0 100644 --- a/src/infrastructure/composition/push_single_action_capability_port_binding.ts +++ b/src/infrastructure/composition/push_single_action_capability_port_binding.ts @@ -13,7 +13,7 @@ import type { } from '../../application/ports/branch_sync_ports'; import type { BoundIssueCommentPublicationPort, - BoundIssuePushNotificationPort, + BoundIssueReopenPort, IssueCommentPublicationPort, IssueNotificationPort, } from '../../application/ports/issue_lifecycle_ports'; @@ -232,13 +232,12 @@ export function bindIssueCommentPublication( }); } -export function bindIssuePushNotification( - port: IssueNotificationPort, +export function bindIssueReopen( + port: Pick, binding: RepositoryCredentialBinding, -): BoundIssuePushNotificationPort { - return Object.freeze({ +): BoundIssueReopenPort { + return Object.freeze({ openIssue: (issueNumber) => port.openIssue(binding.owner, binding.repository, issueNumber, binding.token), - addComment: (issueNumber, comment) => port.addComment(binding.owner, binding.repository, issueNumber, comment, binding.token), }); } diff --git a/src/tooling/__tests__/validate_workflow_contract.test.ts b/src/tooling/__tests__/validate_workflow_contract.test.ts index 22bbc7350..bf7a2af62 100644 --- a/src/tooling/__tests__/validate_workflow_contract.test.ts +++ b/src/tooling/__tests__/validate_workflow_contract.test.ts @@ -86,6 +86,25 @@ const validWorkflow = { }; describe('workflow contract validator', () => { + it('keeps decorative image inputs disabled and explicitly deprecated', () => { + const manifest = yaml.load( + readFileSync(path.join(process.cwd(), 'action.yml'), 'utf8'), + ) as { + inputs: Record; + }; + const toggles = ['images-on-issue', 'images-on-pull-request', 'images-on-commit']; + const pools = Object.keys(manifest.inputs).filter(name => name.startsWith('images-') && !toggles.includes(name)); + + for (const name of toggles) { + expect(manifest.inputs[name]).toMatchObject({ default: 'false' }); + expect(manifest.inputs[name].description).toMatch(/^Deprecated compatibility input\./); + } + expect(pools).toHaveLength(21); + for (const name of pools) { + expect(manifest.inputs[name].description).toMatch(/^Deprecated .* image URL pool retained for input compatibility\.$/); + } + }); + it('keeps the repository validation manifest unique and the queue budget synchronized', () => { const workflowNames = QUEUE_WORKFLOW_MANIFEST.map(entry => entry.workflowName); expect(new Set(workflowNames).size).toBe(workflowNames.length); diff --git a/src/utils/__tests__/comment_watermark.test.ts b/src/utils/__tests__/comment_watermark.test.ts index e09d1fffc..3a9f0d2a5 100644 --- a/src/utils/__tests__/comment_watermark.test.ts +++ b/src/utils/__tests__/comment_watermark.test.ts @@ -1,40 +1,15 @@ -import { COPILOT_MARKETPLACE_URL, getCommentWatermark } from '../comment_watermark'; +import { stripTrailingCommentWatermarks } from '../comment_watermark'; -describe('comment_watermark', () => { - it('exports marketplace URL', () => { - expect(COPILOT_MARKETPLACE_URL).toBe( - 'https://github.com/marketplace/actions/copilot-github-with-super-powers' - ); +describe('legacy comment watermark migration', () => { + it.each([ + 'Made with ❤️ by [vypdev/copilot](https://github.com/marketplace/actions/copilot-github-with-super-powers)', + 'Written by [vypdev/copilot](https://github.com/marketplace/actions/copilot-github-with-super-powers) for commit [abc123](https://github.com/o/r/commit/abc123). This will update automatically on new commits.', + ])('strips a legacy trailing watermark without changing visible content', (watermark) => { + expect(stripTrailingCommentWatermarks(`Useful status\n\n${watermark}`)).toBe('Useful status'); }); - it('returns default watermark when no options', () => { - const w = getCommentWatermark(); - expect(w).toContain('Made with'); - expect(w).toContain('vypdev/copilot'); - expect(w).toContain(COPILOT_MARKETPLACE_URL); - }); - - it('returns bugbot watermark with commit link when options provided', () => { - const w = getCommentWatermark({ commitSha: 'abc123', owner: 'o', repo: 'r' }); - expect(w).toContain('Written by'); - expect(w).toContain('vypdev/copilot'); - expect(w).toContain('abc123'); - expect(w).toContain('github.com/o/r/commit/abc123'); - expect(w).toContain('This will update automatically on new commits'); - }); - - it('URL-encodes owner and repo in commit link when they contain special characters', () => { - const w = getCommentWatermark({ - commitSha: 'abc123', - owner: 'my.org', - repo: 'my-repo', - }); - expect(w).toContain('github.com/my.org/my-repo/commit/abc123'); - const w2 = getCommentWatermark({ - commitSha: 'def456', - owner: 'org with spaces', - repo: 'repo', - }); - expect(w2).toContain('github.com/org%20with%20spaces/repo/commit/def456'); + it('leaves ordinary content and non-trailing legacy text intact', () => { + expect(stripTrailingCommentWatermarks('Useful status')).toBe('Useful status'); + expect(stripTrailingCommentWatermarks('Made with ❤️ by someone else')).toContain('someone else'); }); }); diff --git a/src/utils/comment_watermark.ts b/src/utils/comment_watermark.ts index 651f475f5..8e8697a29 100644 --- a/src/utils/comment_watermark.ts +++ b/src/utils/comment_watermark.ts @@ -1,35 +1,7 @@ -/** - * Watermark appended to comments (issues and PRs) to attribute Copilot. - * Bugbot comments include commit link and note about auto-update on new commits. - */ - -export const COPILOT_MARKETPLACE_URL = - 'https://github.com/marketplace/actions/copilot-github-with-super-powers'; - -const DEFAULT_WATERMARK = `Made with ❤️ by [vypdev/copilot](${COPILOT_MARKETPLACE_URL})`; - -export interface BugbotWatermarkOptions { - commitSha: string; - owner: string; - repo: string; -} - -function commitUrl(owner: string, repo: string, sha: string): string { - return `https://github.com/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commit/${sha}`; -} - -export function getCommentWatermark(options?: BugbotWatermarkOptions): string { - if (options?.commitSha && options?.owner && options?.repo) { - const url = commitUrl(options.owner, options.repo, options.commitSha); - return `Written by [vypdev/copilot](${COPILOT_MARKETPLACE_URL}) for commit [${options.commitSha}](${url}). This will update automatically on new commits.`; - } - return DEFAULT_WATERMARK; -} - const TRAILING_COMMENT_WATERMARK = /\s*(?:Made with ❤️ by|Written by) \[vypdev\/copilot\]\(https:\/\/github\.com\/marketplace\/actions\/copilot-github-with-super-powers\)[^<]*<\/sup>\s*$/u; -/** Removes all trailing Copilot watermarks before a read-modify-write update. */ +/** Removes legacy trailing Copilot watermarks before a read-modify-write update. */ export function stripTrailingCommentWatermarks(comment: string): string { let stripped = comment; while (TRAILING_COMMENT_WATERMARK.test(stripped)) { From 78b5a7a6fc0ea60fa576d89b6af42fb74b996ba2 Mon Sep 17 00:00:00 2001 From: Efra Espada Date: Mon, 14 Sep 2026 15:02:24 +0200 Subject: [PATCH 2/6] codex-semantic-github-publication: isolate review comment reply correlation --- build/api/index.js | 2 - build/cli/index.js | 2 - build/github_action/index.js | 16 ++++-- .../bugbot_review_presentation_policy.test.ts | 38 +++++++++++++- .../copilot_interaction_policy.test.ts | 6 +++ .../publication_identity_policy.test.ts | 4 ++ .../publication_message_catalog.test.ts | 1 + ...semantic_result_publication_policy.test.ts | 45 +++++++++++++++++ .../__tests__/status_command_policy.test.ts | 25 ++++++++++ .../bugbot_review_presentation_policy.ts | 17 +++---- .../__tests__/publish_resume_use_case.test.ts | 50 ++++++++++++++++++- .../reply_publication_workflow.test.ts | 20 ++++++++ .../status_card_publication_workflow.test.ts | 32 ++++++++++++ .../steps/common/publish_resume_workflow.ts | 20 ++++++-- 14 files changed, 255 insertions(+), 23 deletions(-) diff --git a/build/api/index.js b/build/api/index.js index be085a1ac..a31ef7f3a 100644 --- a/build/api/index.js +++ b/build/api/index.js @@ -951,8 +951,6 @@ function renderFindingRow(finding) { : `- ${state} — ${label}`; } function stateLabel(state) { - if (state === 'fixed' || state === 'obsolete' || state === 'dismissed') - return `[x] ${state}`; return `[ ] ${state}`; } function escapeRegExp(value) { diff --git a/build/cli/index.js b/build/cli/index.js index a0ba5f074..c7ee85502 100755 --- a/build/cli/index.js +++ b/build/cli/index.js @@ -42499,8 +42499,6 @@ function renderFindingRow(finding) { : `- ${state} — ${label}`; } function stateLabel(state) { - if (state === 'fixed' || state === 'obsolete' || state === 'dismissed') - return `[x] ${state}`; return `[ ] ${state}`; } function escapeRegExp(value) { diff --git a/build/github_action/index.js b/build/github_action/index.js index dc74516af..05f104aec 100644 --- a/build/github_action/index.js +++ b/build/github_action/index.js @@ -43149,8 +43149,6 @@ function renderFindingRow(finding) { : `- ${state} — ${label}`; } function stateLabel(state) { - if (state === 'fixed' || state === 'obsolete' || state === 'dismissed') - return `[x] ${state}`; return `[ ] ${state}`; } function escapeRegExp(value) { @@ -56713,15 +56711,23 @@ function publicationTarget(source) { return positiveInteger(issueNumber) ? Object.freeze({ kind: 'issue', number: issueNumber }) : undefined; } function requestCorrelationId(source, target) { - const commentId = source.inputs?.comment?.id; - if (positiveInteger(commentId)) - return `comment:${commentId}`; + const commentId = source.inputs?.pull_request_review_comment?.id ?? source.inputs?.comment?.id; + if (positiveInteger(commentId)) { + const transport = safeCorrelationTransport(source.eventName); + return `comment:${transport}:${commentId}`; + } return `event:${(0, publication_identity_policy_1.createSemanticDigest)({ eventName: source.eventName ?? 'unknown', action: source.inputs?.action ?? '', target: target ?? null, })}`; } +function safeCorrelationTransport(eventName) { + const value = eventName?.trim() || 'unknown'; + return /^[A-Za-z0-9._-]{1,64}$/u.test(value) + ? value + : `event-${(0, publication_identity_policy_1.createSemanticDigest)(value)}`; +} function positiveInteger(value) { return typeof value === 'number' && Number.isSafeInteger(value) && value > 0; } diff --git a/src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts b/src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts index f4c4ebb39..9c5e6b173 100644 --- a/src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts +++ b/src/application/policies/__tests__/bugbot_review_presentation_policy.test.ts @@ -114,8 +114,9 @@ describe('Bugbot review presentation', () => { itemsFetched: 200, itemsRetained: 200, omittedItems: 1, - truncatedItems: 0, + truncatedItems: 2, limitReached: true, + providerLimitReached: true, }], }; const body = renderBugbotStatusCard(buildBugbotReviewProjection({ @@ -127,11 +128,22 @@ describe('Bugbot review presentation', () => { expect(body).toContain('cannot declare the whole pull request clean'); expect(body).toContain('issue-comments: partial; retained=200, omitted=1'); + expect(body).toContain('truncated=2'); + expect(body).toContain('provider page limit reached'); expect(body).toContain('Inspect the omitted items or reduce the PR scope'); expect(body).toContain('Rerun the review only after changing'); expect(body).not.toContain('Run `/copilot recheck`'); expect(body).not.toContain('No active findings'); expect(body).not.toContain('No action required'); + const spanish = renderBugbotStatusCard(buildBugbotReviewProjection({ + pullRequestNumber: 358, + analyzedHeadSha: head, + coverage: partialCoverage, + findings: [], + }), 'es-ES', links); + expect(spanish).toContain('## Bugbot: revisión incompleta'); + expect(spanish).toContain('Repite la revisión solo después de cambiar'); + expect(spanish).toContain('Cobertura incompleta'); }); it('renders empty and long finding lists without requiring a workflow-run link', () => { @@ -149,6 +161,15 @@ describe('Bugbot review presentation', () => { const body = renderBugbotStatusCard(long, 'en-US', noRunLinks); expect(body).not.toContain('Workflow run'); expect(body).not.toContain('### Findings'); + + const actionable = buildBugbotReviewProjection({ + pullRequestNumber: 358, + analyzedHeadSha: head, + coverage: completeCoverage, + findings: Array.from({ length: 22 }, (_, index) => ({ id: `open-${index}`, state: 'open' as const })), + }); + expect(renderBugbotStatusCard(actionable, 'en-US', noRunLinks)).toContain('…and 2 more.'); + expect(renderBugbotStatusCard(actionable, 'es-ES', noRunLinks)).toContain('…y 2 más.'); }); it('rejects malformed and outdated status markers', () => { @@ -257,6 +278,21 @@ describe('Bugbot review presentation', () => { expect(clean).not.toContain('All findings originating'); expect(active).toContain('con seguimiento individual'); expect(active).toContain('overflow histórico sin thread individual'); + expect(renderBugbotReviewSnapshot(original, { + reviewIdentity: '77', analyzedHeadSha: head, currentHeadSha: head, + projectionDigest: '12345678', coverageStatus: 'complete', findings: projection(['fixed']).findings, + locale: 'es-ES', statusUrl: links.pullRequestUrl, + })).toContain('Ningún hallazgo con seguimiento individual'); + }); + + it('renders Spanish partial-coverage historical status', () => { + const body = renderBugbotReviewSnapshot(null, { + reviewIdentity: '77', analyzedHeadSha: head, currentHeadSha: head, + projectionDigest: '12345678', coverageStatus: 'partial', findings: [], + locale: 'es-ES', statusUrl: links.pullRequestUrl, + }); + expect(body).toContain('La cobertura global es parcial'); + expect(body).toContain('Última reconciliación en'); }); it('sanitizes titles before publishing them in the status card', () => { diff --git a/src/application/policies/__tests__/copilot_interaction_policy.test.ts b/src/application/policies/__tests__/copilot_interaction_policy.test.ts index 0a61766db..780718c54 100644 --- a/src/application/policies/__tests__/copilot_interaction_policy.test.ts +++ b/src/application/policies/__tests__/copilot_interaction_policy.test.ts @@ -21,6 +21,12 @@ describe('Copilot interaction policy', () => { expect(help).toContain('@vypbot'); }); + it('renders the supported command reference and welcome copy in Spanish', () => { + expect(buildCopilotHelpMessage('vypbot', 'es-MX')).toContain('## Comandos de Copilot'); + expect(buildCopilotHelpMessage('vypbot', 'es-MX')).toContain('/copilot implement '); + expect(buildCopilotWelcomeMessage('vypbot', 'es-ES')).toContain('Hola, soy **@vypbot**'); + }); + it('renders a marked one-time issue welcome message', () => { const welcome = buildCopilotWelcomeMessage('vypbot'); expect(welcome.startsWith(COPILOT_WELCOME_MARKER)).toBe(true); diff --git a/src/application/policies/__tests__/publication_identity_policy.test.ts b/src/application/policies/__tests__/publication_identity_policy.test.ts index 748b26c6c..999381ba8 100644 --- a/src/application/policies/__tests__/publication_identity_policy.test.ts +++ b/src/application/policies/__tests__/publication_identity_policy.test.ts @@ -75,5 +75,9 @@ describe('publication identity policy', () => { expect(() => buildPublicationReplyMarker({ target: 'issue:42', correlationId: 'unsafe value', messageKey: 'copilot-help', digest: '0123abcd', })).toThrow('unsafe identity'); + expect(() => buildPublicationReplyMarker({ + target: 'issue:42', correlationId: 'comment:99', messageKey: 'copilot-help', digest: 'invalid', + })).toThrow('invalid digest'); + expect(parsePublicationReplyMarker(null)).toBeUndefined(); }); }); diff --git a/src/application/policies/__tests__/publication_message_catalog.test.ts b/src/application/policies/__tests__/publication_message_catalog.test.ts index 51c489538..c737464dd 100644 --- a/src/application/policies/__tests__/publication_message_catalog.test.ts +++ b/src/application/policies/__tests__/publication_message_catalog.test.ts @@ -11,6 +11,7 @@ describe('publication message catalog', () => { ['es-ES', 'es-ES', false], ['es-MX', 'es-ES', false], ['fr-FR', 'en-US', true], + ['', 'en-US', false], ] as const)('resolves %s through deterministic catalogs', (locale, catalogLocale, fallback) => { const resolved = resolveStaticPublicationCatalog(locale); expect(resolved.catalog.locale).toBe(catalogLocale); diff --git a/src/application/policies/__tests__/semantic_result_publication_policy.test.ts b/src/application/policies/__tests__/semantic_result_publication_policy.test.ts index 13fb8578b..90861cdb7 100644 --- a/src/application/policies/__tests__/semantic_result_publication_policy.test.ts +++ b/src/application/policies/__tests__/semantic_result_publication_policy.test.ts @@ -116,6 +116,51 @@ describe('semantic result publication policy', () => { expect(body).not.toContain('banned'); }); + it('renders the English access-policy fallback', () => { + const [intent] = selectSemanticReplyIntents({ + locale: 'fr-FR', target: { kind: 'issue', number: 8 }, correlationId: 'event:abc12345', + results: [new Result({ + id: 'CloseNotAllowedIssueUseCase', success: true, executed: true, + payload: { publication: { kind: 'access-policy' } }, + })], + }); + + expect(renderSemanticReply(intent)).toContain('Issue closed: contributor access required'); + }); + + it('sanitizes reply correlation and falls back to the trusted bot login', () => { + const [intent] = selectSemanticReplyIntents({ + locale: 'en-US', target: { kind: 'issue', number: 8 }, correlationId: 'unsafe\ncorrelation', botLogin: ' trusted-bot ', + results: [new Result({ + id: 'Comment.Help', success: true, executed: true, + payload: { publication: { kind: 'help', botLogin: ' ' } }, + })], + }); + + expect(intent.correlationId).toMatch(/^digest:[a-f0-9]{16}$/u); + expect(renderSemanticReply(intent)).toContain('@trusted-bot'); + }); + + it('copies optional finding state in a status reply and accepts an existing plan fingerprint', () => { + const [status] = selectSemanticReplyIntents({ + locale: 'en-US', target: { kind: 'issue', number: 8 }, correlationId: 'comment:issue_comment:8', + results: [new Result({ id: 'Comment.Status', success: true, executed: true, payload: { status: { + owner: 'acme', repository: 'widgets', event: 'issues', action: 'opened', target: 'issue', + issueLabels: [], pullRequestLabels: [], pullRequestDescriptionMode: 'disabled', + findingStates: { open: 1, reopened: 0, verificationRequired: 0, unknown: 0, resolved: 2 }, + } } })], + }); + const [plan] = selectSemanticStatusIntents({ + locale: 'en-US', results: [new Result({ + id: 'RecommendStepsUseCase', success: true, executed: true, + payload: { issueNumber: 8, recommendedSteps: '1. Implement', recommendationState: { issueDescriptionFingerprint: 'abcdef12' } }, + })], + }); + + expect(status.projection).toMatchObject({ kind: 'status-command', snapshot: { findingStates: { open: 1 } } }); + expect(plan.sourceVersion).toBe('issue-body:abcdef12'); + }); + it('omits malformed, failed, uncorrelated, or untargeted replies', () => { const result = new Result({ id: 'Comment.Help', success: true, executed: true, payload: { publication: { kind: 'help' } } }); expect(selectSemanticReplyIntents({ locale: 'en-US', results: [result] })).toEqual([]); diff --git a/src/application/policies/__tests__/status_command_policy.test.ts b/src/application/policies/__tests__/status_command_policy.test.ts index eebcbc81d..492792606 100644 --- a/src/application/policies/__tests__/status_command_policy.test.ts +++ b/src/application/policies/__tests__/status_command_policy.test.ts @@ -140,4 +140,29 @@ describe('status command policy', () => { expect(result.executed).toBe(true); expect(result.steps[0]).toContain('## Copilot status'); }); + + it('renders Spanish status defaults and invalid evidence explicitly', () => { + const body = formatCopilotStatus({ + owner: 'acme', repository: 'demo', event: 'issues', action: '', target: 'issue', + pullRequestDescriptionMode: 'disabled', issueLabels: [], pullRequestLabels: [], + findingStateEvidence: 'invalid', + }, 'es-MX'); + + expect(body).toContain('## Estado de Copilot'); + expect(body).toContain('Rama:** desconocida'); + expect(body).toContain('evidencia no válida'); + }); + + it('renders Spanish status with identifiers, labels, and finding counts', () => { + const body = formatCopilotStatus({ + owner: 'acme', repository: 'demo', event: 'pull_request', action: 'opened', target: 'pull-request', + issueNumber: 17, pullRequestNumber: 21, branch: 'feature/17-demo', lifecycle: 'reviewing', + waitingFor: 'maintainer', pullRequestDescriptionMode: 'append', issueLabels: ['state:in-progress'], + pullRequestLabels: ['state:reviewing'], + findingStates: { open: 1, reopened: 2, verificationRequired: 3, unknown: 4, resolved: 5 }, + }, 'es-ES'); + + expect(body).toContain('pull-request #17 / PR #21'); + expect(body).toContain('1 abiertos, 2 reabiertos, 3 requieren verificación, 4 desconocidos, 5 resueltos'); + }); }); diff --git a/src/application/policies/bugbot_review_presentation_policy.ts b/src/application/policies/bugbot_review_presentation_policy.ts index 1701c7d95..148cabf54 100644 --- a/src/application/policies/bugbot_review_presentation_policy.ts +++ b/src/application/policies/bugbot_review_presentation_policy.ts @@ -40,13 +40,13 @@ export function renderBugbotStatusCard( locale: string, links: BugbotPresentationLinks, ): string { - const language = normalizeBugbotPresentationLocale(locale); - const actionable = projection.findings.filter((finding) => - isBugbotActionableState(finding.state), - ); - const unknown = projection.counts.unknown; - const partialCoverage = projection.coverage.status === 'partial'; - const shortHead = projection.verifiedHeadSha.slice(0, 7); + const language = normalizeBugbotPresentationLocale(locale); + const actionable = projection.findings.filter((finding) => + isBugbotActionableState(finding.state), + ); + const unknown = projection.counts.unknown; + const partialCoverage = projection.coverage.status === 'partial'; + const shortHead = projection.verifiedHeadSha.slice(0, 7); const heading = partialCoverage ? language === 'es-ES' ? '## Bugbot: revisión incompleta' : '## Bugbot: review incomplete' : projection.outcome === 'partial' || projection.outcome === 'failed' || unknown > 0 @@ -88,7 +88,7 @@ export function renderBugbotStatusCard( ? 'Revisa los threads enlazados o comenta `/copilot fix all`.' : 'Review the linked threads or comment `/copilot fix all`.' : undefined; - const findingsHeading = language === 'es-ES' ? '### Hallazgos' : '### Findings'; + const findingsHeading = language === 'es-ES' ? '### Hallazgos' : '### Findings'; const visibleFindings = projection.findings.filter((finding) => isBugbotActionableState(finding.state) || finding.state === 'unknown', ); @@ -252,7 +252,6 @@ function renderFindingRow(finding: BugbotProjectedFinding): string { } function stateLabel(state: BugbotFindingState): string { - if (state === 'fixed' || state === 'obsolete' || state === 'dismissed') return `[x] ${state}`; return `[ ] ${state}`; } diff --git a/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts b/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts index 96a70d107..54944a73f 100644 --- a/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts +++ b/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts @@ -93,11 +93,48 @@ describe('PublishResultUseCase semantic compatibility boundary', () => { await new PublishResultUseCase(comments).invoke(value); expect(comments.addComment).toHaveBeenCalledTimes(1); - expect(comments.values[0].body).toContain('correlation="comment:99"'); + expect(comments.values[0].body).toContain('correlation="comment:issue_comment:99"'); expect(comments.values[0].body).toContain('## Copilot commands'); expect(comments.values[0].body).not.toContain('legacy wrapper'); }); + it('namespaces equal numeric comment ids by GitHub transport', () => { + const issueComment = projectPublishResultContext(source([], { + eventName: 'issue_comment', inputs: { action: 'created', comment: { id: 99 } }, + })); + const reviewComment = projectPublishResultContext(source([], { + eventName: 'pull_request_review_comment', inputs: { action: 'created', pull_request_review_comment: { id: 99 } }, + })); + + expect(issueComment.requestCorrelationId).toBe('comment:issue_comment:99'); + expect(reviewComment.requestCorrelationId).toBe('comment:pull_request_review_comment:99'); + expect(issueComment.requestCorrelationId).not.toBe(reviewComment.requestCorrelationId); + }); + + it('uses each review-comment id instead of collapsing replies into the event fallback', () => { + const first = projectPublishResultContext(source([], { + eventName: 'pull_request_review_comment', + inputs: { action: 'created', pull_request_review_comment: { id: 99 } }, + })); + const second = projectPublishResultContext(source([], { + eventName: 'pull_request_review_comment', + inputs: { action: 'created', pull_request_review_comment: { id: 100 } }, + })); + + expect(first.requestCorrelationId).toBe('comment:pull_request_review_comment:99'); + expect(second.requestCorrelationId).toBe('comment:pull_request_review_comment:100'); + expect(first.requestCorrelationId).not.toBe(second.requestCorrelationId); + }); + + it('bounds an unexpected transport value before embedding it in a marker', () => { + const context = projectPublishResultContext(source([], { + eventName: 'unsafe transport\n', + inputs: { action: 'created', comment: { id: 99 } }, + })); + + expect(context.requestCorrelationId).toMatch(/^comment:event-[a-f0-9]{16}:99$/u); + }); + it('does not mutate an unchanged plan card on replay', async () => { const comments = inMemoryComments(); const useCase = new PublishResultUseCase(comments); @@ -176,4 +213,15 @@ describe('PublishResultUseCase semantic compatibility boundary', () => { expect(failure).toMatchObject({ success: false, executed: true }); expect(failure?.errors[0]).toMatchObject({ code: 'provider.unavailable' }); }); + + it('projects fallback issue targets and recursively copies array payloads', () => { + const mutable = [{ nested: ['value'] }]; + const context = projectPublishResultContext(source([ + new Result({ id: 'metadata', success: true, executed: true, payload: mutable }), + ], { issue: undefined, issueNumber: 42 })); + mutable[0].nested[0] = 'changed'; + + expect(context.target).toEqual({ kind: 'issue', number: 42 }); + expect(context.results[0].payload).toEqual([{ nested: ['value'] }]); + }); }); diff --git a/src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts b/src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts index 4171bb1b7..090a93731 100644 --- a/src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts +++ b/src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts @@ -66,4 +66,24 @@ describe('reply publication workflow', () => { }); expect(repository.listIssueComments).not.toHaveBeenCalled(); }); + + it('tolerates create visibility lag without guessing a reply id', async () => { + const repository = ports(); + repository.addComment.mockImplementation(async () => undefined); + await expect(reconcileReply(context(), repository)).resolves.toEqual({ effect: 'created', duplicatesCompacted: 0 }); + }); + + it('links a compacted pull-request reply to the canonical PR comment', async () => { + const value = { ...intent(), target: { kind: 'pull-request' as const, number: 9 }, locale: 'es-ES' }; + const seeded = ports(); + await reconcileReply({ ...context(), intent: value }, seeded); + const repository = ports([ + { id: 7, body: seeded.comments[0].body, user: { login: 'vypbot' } }, + { id: 4, body: seeded.comments[0].body, user: { login: 'vypbot' } }, + ]); + + await reconcileReply({ ...context(), intent: value }, repository); + expect(repository.comments.find(comment => comment.id === 7)?.body).toContain('/pull/9#issuecomment-4'); + expect(repository.comments.find(comment => comment.id === 7)?.body).toContain('respuesta duplicada'); + }); }); diff --git a/src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts b/src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts index e2da009ac..0f5520ae2 100644 --- a/src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts +++ b/src/application/usecases/steps/common/__tests__/status_card_publication_workflow.test.ts @@ -88,4 +88,36 @@ describe('status card publication workflow', () => { repository.addComment.mockImplementation(async () => undefined); await expect(reconcileStatusCard(context(), repository)).resolves.toEqual({ effect: 'created', duplicatesCompacted: 0 }); }); + + it('preserves the created outcome when post-create discovery finds a stale canonical card', async () => { + const value = intent('Latest'); + const repository = ports([{ id: 1, body: renderSemanticStatus(intent('Stale')), user: { login: 'vypbot' } }]); + let reads = 0; + repository.listIssueComments.mockImplementation(async () => { + reads += 1; + return reads === 1 ? [] : repository.comments.map(comment => ({ ...comment })); + }); + + await expect(reconcileStatusCard(context(value), repository)).resolves.toMatchObject({ + effect: 'created', canonicalCommentId: 1, + }); + expect(repository.comments[0].body).toContain('Latest'); + }); + + it('links compacted pull-request cards to the canonical PR comment', async () => { + const value = { + ...intent('Latest'), + identity: { ...intent('Latest').identity, target: { kind: 'pull-request' as const, number: 9 } }, + locale: 'es-ES', + }; + const body = renderSemanticStatus(value); + const repository = ports([ + { id: 7, body, user: { login: 'vypbot' } }, + { id: 4, body, user: { login: 'vypbot' } }, + ]); + + await reconcileStatusCard(context(value), repository); + expect(repository.comments.find(comment => comment.id === 7)?.body).toContain('/pull/9#issuecomment-4'); + expect(repository.comments.find(comment => comment.id === 7)?.body).toContain('tarjeta canónica'); + }); }); diff --git a/src/application/usecases/steps/common/publish_resume_workflow.ts b/src/application/usecases/steps/common/publish_resume_workflow.ts index 1ee82d0bd..7315abfc4 100644 --- a/src/application/usecases/steps/common/publish_resume_workflow.ts +++ b/src/application/usecases/steps/common/publish_resume_workflow.ts @@ -27,7 +27,11 @@ export interface PublishResultContextSource { readonly issueNumber?: number; readonly issue?: { readonly number?: number }; readonly pullRequest?: { readonly number?: number }; - readonly inputs?: { readonly action?: string; readonly comment?: { readonly id?: number } }; + readonly inputs?: { + readonly action?: string; + readonly comment?: { readonly id?: number }; + readonly pull_request_review_comment?: { readonly id?: number }; + }; readonly locale?: { readonly issue: string; readonly pullRequest: string }; readonly currentConfiguration: { readonly results: readonly Result[] }; } @@ -111,8 +115,11 @@ function publicationTarget(source: PublishResultContextSource): PublicationTarge } function requestCorrelationId(source: PublishResultContextSource, target: PublicationTarget | undefined): string { - const commentId = source.inputs?.comment?.id; - if (positiveInteger(commentId)) return `comment:${commentId}`; + const commentId = source.inputs?.pull_request_review_comment?.id ?? source.inputs?.comment?.id; + if (positiveInteger(commentId)) { + const transport = safeCorrelationTransport(source.eventName); + return `comment:${transport}:${commentId}`; + } return `event:${createSemanticDigest({ eventName: source.eventName ?? 'unknown', action: source.inputs?.action ?? '', @@ -120,6 +127,13 @@ function requestCorrelationId(source: PublishResultContextSource, target: Public })}`; } +function safeCorrelationTransport(eventName: string | undefined): string { + const value = eventName?.trim() || 'unknown'; + return /^[A-Za-z0-9._-]{1,64}$/u.test(value) + ? value + : `event-${createSemanticDigest(value)}`; +} + function positiveInteger(value: unknown): value is number { return typeof value === 'number' && Number.isSafeInteger(value) && value > 0; } From 01aef29c79e7f61595c293d86652e72c2877f234 Mon Sep 17 00:00:00 2001 From: Efra Espada Date: Mon, 14 Sep 2026 15:19:09 +0200 Subject: [PATCH 3/6] codex-semantic-github-publication: isolate push and PR review ownership --- .github/workflows/copilot_commit.yml | 2 +- .github/workflows/copilot_pull_request.yml | 2 +- build/cli/index.js | 7 +++- build/github_action/index.js | 7 +++- docs/bugbot/configuration.mdx | 2 +- docs/bugbot/detection.mdx | 3 +- docs/bugbot/examples.mdx | 2 +- docs/bugbot/how-it-works.mdx | 2 +- docs/development/testing.mdx | 4 +- docs/features.mdx | 13 +++--- docs/how-to-use.mdx | 2 +- docs/index.mdx | 2 +- docs/issues/index.mdx | 2 +- docs/pull-requests/capabilities.mdx | 2 +- docs/pull-requests/examples.mdx | 2 +- docs/pull-requests/index.mdx | 4 +- docs/pull-requests/workflow-setup.mdx | 13 +++--- .../operations/troubleshooting.mdx | 2 +- scripts/validate-workflow-contract.cjs | 16 +++++--- setup/workflows/copilot_commit.yml | 2 +- setup/workflows/copilot_pull_request.yml | 2 +- specs/CATALOG.md | 14 +++---- specs/bugbot-review-state-reconciliation.md | 41 ++++++++++++------- specs/catalog.json | 6 ++- ...ssue-and-pull-request-context-hardening.md | 27 ++++++++---- ...tic-github-publication-and-notification.md | 20 +++++++++ .../__tests__/commit_use_case.test.ts | 19 +++++++++ src/application/usecases/commit_use_case.ts | 12 ++++-- .../validate_workflow_contract.test.ts | 20 ++++++++- 29 files changed, 179 insertions(+), 73 deletions(-) diff --git a/.github/workflows/copilot_commit.yml b/.github/workflows/copilot_commit.yml index 274773957..e485295c3 100644 --- a/.github/workflows/copilot_commit.yml +++ b/.github/workflows/copilot_commit.yml @@ -14,7 +14,7 @@ jobs: runs-on: [self-hosted, codex] timeout-minutes: 120 concurrency: - group: copilot-bugbot-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }} + group: copilot-push-${{ github.repository }}-${{ github.ref_name }} cancel-in-progress: true permissions: contents: read diff --git a/.github/workflows/copilot_pull_request.yml b/.github/workflows/copilot_pull_request.yml index 9d952870a..77b081e99 100644 --- a/.github/workflows/copilot_pull_request.yml +++ b/.github/workflows/copilot_pull_request.yml @@ -27,7 +27,7 @@ jobs: runs-on: [self-hosted, codex] timeout-minutes: 120 concurrency: - group: copilot-bugbot-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }} + group: copilot-pr-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }} cancel-in-progress: ${{ github.event_name != 'pull_request' || github.event.action != 'edited' }} permissions: checks: write diff --git a/build/cli/index.js b/build/cli/index.js index c7ee85502..3009b4d9e 100755 --- a/build/cli/index.js +++ b/build/cli/index.js @@ -49080,7 +49080,12 @@ class CommitUseCase { || Boolean(this.actorAuthorizationPort && await this.actorAuthorizationPort.isActorAllowedToModifyFiles(param.owner, param.repo, param.actor, param.tokens.token)); if (agentAllowed) { results.push(...(await this.checkProgressUseCase.invoke((0, push_single_action_contexts_1.projectProgressContext)(param)))); - results.push(...(await this.detectPotentialProblemsUseCase.invoke((0, bugbot_review_operation_context_1.projectBugbotReviewOperationContext)(param)))); + if (param.pullRequest.number > 0) { + (0, logging_ports_1.logInfo)(`Skipping push Bugbot analysis because pull request #${param.pullRequest.number} owns review for this head.`); + } + else { + results.push(...(await this.detectPotentialProblemsUseCase.invoke((0, bugbot_review_operation_context_1.projectBugbotReviewOperationContext)(param)))); + } } else { (0, logging_ports_1.logInfo)('Skipping push agent analysis because ai-members-only is enabled and the actor is not authorized.'); diff --git a/build/github_action/index.js b/build/github_action/index.js index 05f104aec..edfcb5653 100644 --- a/build/github_action/index.js +++ b/build/github_action/index.js @@ -49898,7 +49898,12 @@ class CommitUseCase { || Boolean(this.actorAuthorizationPort && await this.actorAuthorizationPort.isActorAllowedToModifyFiles(param.owner, param.repo, param.actor, param.tokens.token)); if (agentAllowed) { results.push(...(await this.checkProgressUseCase.invoke((0, push_single_action_contexts_1.projectProgressContext)(param)))); - results.push(...(await this.detectPotentialProblemsUseCase.invoke((0, bugbot_review_operation_context_1.projectBugbotReviewOperationContext)(param)))); + if (param.pullRequest.number > 0) { + (0, logging_ports_1.logInfo)(`Skipping push Bugbot analysis because pull request #${param.pullRequest.number} owns review for this head.`); + } + else { + results.push(...(await this.detectPotentialProblemsUseCase.invoke((0, bugbot_review_operation_context_1.projectBugbotReviewOperationContext)(param)))); + } } else { (0, logging_ports_1.logInfo)('Skipping push agent analysis because ai-members-only is enabled and the actor is not authorized.'); diff --git a/docs/bugbot/configuration.mdx b/docs/bugbot/configuration.mdx index 7cf981974..24dfd77fc 100644 --- a/docs/bugbot/configuration.mdx +++ b/docs/bugbot/configuration.mdx @@ -121,7 +121,7 @@ Use repository-specific review and protected-branch controls in addition to igno Detection is read-oriented, but publication requires the GitHub permissions needed to create issue comments or pull-request review comments. Autofix and user-request workflows additionally require write permission and must be restricted to trusted actors and trusted code. -PR reconciliation also reads submitted reviews and updates their generated status blocks, then upserts one issue-comment status card. Keep `pull-requests: write` and `issues: write` available to the configured PAT. No additional Bugbot toggle or secret is required. The shipped Commit and Pull Request workflows use the same repository/branch native concurrency key; code-change and review events cancel obsolete analyses, while `pull_request: edited` uses a conditional `cancel-in-progress` expression and waits instead of preempting an active review. Copy both templates together so push and PR synchronization reviews converge on one current head. +PR reconciliation also reads submitted reviews and updates their generated status blocks, then upserts one issue-comment status card. Keep `pull-requests: write` and `issues: write` available to the configured PAT. No additional Bugbot toggle or secret is required. The shipped Commit and Pull Request workflows use distinct repository/branch native concurrency keys, so they cannot cancel one another. When Commit discovers an open same-repository PR, it skips Bugbot and lets `pull_request:synchronize` own review for that head; branches without a PR retain push-time review. A metadata-only `pull_request: edited` run waits instead of preempting an active PR review. Copy both templates together so this ownership contract remains intact. Do not expose provider credentials to fork code, untrusted pull requests, or `pull_request_target` workflows that execute attacker-controlled code. Prefer private repositories, protected branches, reviewed workflows, approved environments, and controlled runners. diff --git a/docs/bugbot/detection.mdx b/docs/bugbot/detection.mdx index 894bfe6c6..0b19d990e 100644 --- a/docs/bugbot/detection.mdx +++ b/docs/bugbot/detection.mdx @@ -27,12 +27,13 @@ its findings as line comments on the current diff. ### 2. On every push (commit workflow) -When you push to a branch that is **linked to an issue** (e.g. a feature or bugfix branch created by Copilot), the **Commit** workflow runs. If an agent CLI is configured, the action runs **DetectPotentialProblemsUseCase**: it fetches and reviews the exact event `before..after` range, loads existing finding context, and then publishes new findings and updates or marks as resolved any previous findings that no longer apply. The bounded fetch includes `after` and its parent. New-branch pushes, whose `before` value is GitHub's all-zero sentinel, and force-pushes whose previous object is no longer reachable can therefore fall back to the current commit parent without aborting the workflow or downloading full history. +When you push to a branch that is **linked to an issue** (e.g. a feature or bugfix branch created by Copilot), the **Commit** workflow runs. If an agent CLI is configured and there is no open PR, the action runs **DetectPotentialProblemsUseCase**: it fetches and reviews the exact event `before..after` range, loads existing finding context, and then publishes new findings and updates or marks as resolved any previous findings that no longer apply. The bounded fetch includes `after` and its parent. New-branch pushes, whose `before` value is GitHub's all-zero sentinel, and force-pushes whose previous object is no longer reachable can therefore fall back to the current commit parent without aborting the workflow or downloading full history. When an open same-repository PR exists, Commit keeps issue progress current but skips Bugbot because the paired PR `synchronize` event owns review for that head. **Requirements:** - The push must be to a branch that has an associated issue (the action resolves the issue number from the branch name or from project/linking). - The Commit workflow must include the Copilot step (see [Examples → Push workflow](/bugbot/examples#push-workflow-with-bugbot)). +- No open PR may own review for the branch; otherwise the PR synchronization workflow runs Bugbot. If the branch is not linked to an issue (e.g. `issueNumber === -1`), detection is skipped. diff --git a/docs/bugbot/examples.mdx b/docs/bugbot/examples.mdx index c664e0ad0..6a2ce0ecf 100644 --- a/docs/bugbot/examples.mdx +++ b/docs/bugbot/examples.mdx @@ -11,7 +11,7 @@ This page provides **concrete examples**: workflows that enable Bugbot detection ## Push workflow (with Bugbot) -The **Commit** workflow runs on **push** to branches (typically excluding `main` and `develop`). If an agent CLI is configured, Bugbot detection runs automatically for branches linked to an issue. +The **Commit** workflow runs on **push** to branches (typically excluding `main` and `develop`). If an agent CLI is configured, Bugbot detection runs automatically for issue-linked branches without an open PR. After a PR opens, Commit retains issue progress work and the PR `synchronize` workflow exclusively owns Bugbot review for each new head. ```yaml name: Copilot - Commit diff --git a/docs/bugbot/how-it-works.mdx b/docs/bugbot/how-it-works.mdx index f1a584f3c..c0e07861e 100644 --- a/docs/bugbot/how-it-works.mdx +++ b/docs/bugbot/how-it-works.mdx @@ -81,7 +81,7 @@ This page describes the **internal flow** of Bugbot: how detection runs, how the 7. **Re-read and project:** After mutations, Bugbot verifies the PR head, re-reads linked-issue findings, reviews, child comments, native thread facts, and the PR conversation concurrently, and then verifies the head again. If the head changed while those reads were in flight, the whole snapshot is discarded as superseded. Each surface records whether its read was verified, failed, or not applicable, and each non-clean issue and PR destination must be observed independently; one visible or clean destination cannot hide another that is open, unverifiable, or missing. Pure lifecycle, provider-projection, and reconciliation-plan policies derive `open`, `reopened`, `fixed`, `obsolete`, `dismissed`, `verification-required`, or `unknown`. A dedicated presentation use case then updates at most 20 affected historical review blocks per run with bounded concurrency, upserts the oldest trusted **Bugbot status** card, and feeds the Result, lifecycle labels, Job Summary, Check Run, and telemetry. Missing or malformed owned evidence fails closed; it is never presented as clean. User-facing PR, commit, run, review, and finding links come only from authenticated provider adapters. Finding links are accepted only when they belong to the same HTTPS server and repository, including GitHub Enterprise installations. -Review and Commit workflow templates share a repository-and-branch concurrency key. A newer code-change or review event cancels an older review of the same branch. A `pull_request: edited` event uses the same serialization key but cannot cancel the running review; it waits, and redundant pending edits collapse to the newest event. When it later runs, it keeps its native workflow result and Job Summary but does not create a `Copilot / Review` Check because it has no Bugbot telemetry. The Check name is reserved for exactly one structurally valid analysis snapshot for the exact head; duplicate telemetry or a valid snapshot beside malformed telemetry is rejected as ambiguous. Title/body normalization can therefore neither cancel nor later hide a real `synchronize` review. Application head guards and idempotent provider writes still protect partial/canceled transitions. Other durable mutation workflows retain their workflow-local queue. +Review and Commit workflow templates use distinct repository-and-branch concurrency keys. Each can cancel only a superseded run from the same event owner; a paired `push` and `pull_request:synchronize` therefore cannot cancel one another. Once Commit discovers an open same-repository PR, it retains issue progress and native push work but skips Bugbot, because the PR synchronization event exclusively owns review for that head. Branches without an open PR still receive push-time, issue-targeted Bugbot review. Fork PR jobs remain excluded by the workflow's same-repository admission gate. A `pull_request: edited` event uses the PR key but cannot cancel a running PR review; it waits, and redundant pending edits collapse to the newest event. When it later runs, it keeps its native workflow result and Job Summary but does not create a `Copilot / Review` Check because it has no Bugbot telemetry. The Check name is reserved for exactly one structurally valid analysis snapshot for the exact head; duplicate telemetry or a valid snapshot beside malformed telemetry is rejected as ambiguous. Application head guards and idempotent provider writes still protect partial/canceled transitions. Other durable mutation workflows retain their workflow-local queue. Every exit path emits optional content-free telemetry, including canonical selection reason, logical and raw request counts, the fixed concurrency limit, diff --git a/docs/development/testing.mdx b/docs/development/testing.mdx index eaba047d7..41d3cee9a 100644 --- a/docs/development/testing.mdx +++ b/docs/development/testing.mdx @@ -55,8 +55,8 @@ rate-limited 403 headers, bounded fallback backoff/jitter, and the absolute dead The workflow contract test keeps the nine queue-bearing workflow names synchronized with the validator and verifies the `90m queue / 120m job` budget, required runners, action inputs, missing/short timeouts, forbidden durable-mutation concurrency, and the -exact shared branch concurrency contract permitted only for Commit/Pull Request -Bugbot jobs. +distinct branch concurrency and event-ownership contracts for Commit and Pull +Request jobs. GitHub lifecycle tests cover admission before queue polling, PAT-owner normal-run discard, valid single-action preservation, identity lookup failure, and the absence diff --git a/docs/features.mdx b/docs/features.mdx index ff93d6817..b3fff9961 100644 --- a/docs/features.mdx +++ b/docs/features.mdx @@ -158,10 +158,10 @@ are deprecated. ## Workflow concurrency and sequential execution - **Two coordination policies:** durable mutation workflows preserve every admitted event in a workflow-local queue. The shipped Commit and Pull Request workflows instead share a repository/branch native concurrency group, so a newer code revision cancels a superseded review of that same branch. + **Two coordination policies:** durable mutation workflows preserve every admitted event in a workflow-local queue. Commit and Pull Request each have a separate repository/branch latest-revision lane, so paired events cannot cancel one another. -GitHub's native [concurrency](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency) can cancel in-progress runs when a newer one starts and can retain only one pending run. That behavior is appropriate for replaceable code-review revisions: `copilot_commit.yml` and `copilot_pull_request.yml` use the exact shared key `copilot-bugbot-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}`. The PR workflow conditionally disables cancellation for `pull_request: edited`, so metadata normalization waits instead of preempting the active review; newer code/review events still cancel obsolete analysis. Application-level head guards prevent an older run from updating a newer PR, and the next run repairs any durable half-transition discovered after cancellation. +GitHub's native [concurrency](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency) can cancel in-progress runs when a newer one starts and can retain only one pending run. The replaceable branch jobs therefore use event-specific keys: Commit uses `copilot-push-${{ github.repository }}-${{ github.ref_name }}` and Pull Request uses `copilot-pr-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}`. Each cancels only its own obsolete revision. When Commit discovers an open same-repository PR, it still synchronizes issue progress but skips Bugbot; the PR `synchronize` event exclusively owns review for that head. This prevents duplicate reviews without letting one event cancel useful work from the other. Branches without an open PR retain push-time Bugbot. The PR workflow conditionally disables cancellation for `pull_request: edited`, so metadata normalization waits instead of preempting the active PR review. Application-level head guards prevent an older run from updating a newer PR, and the next run repairs any durable half-transition discovered after cancellation. For non-replaceable issue and comment mutations, Copilot adds an application-level queue per workflow: every started run waits for earlier active runs of that same workflow, so intermediate events are not discarded by a native concurrency group. Runs triggered by the PAT owner that would only re-trigger the normal pipeline complete before entering this queue. @@ -186,9 +186,10 @@ exact counting must detect a matching run on a later page. Querying only active states keeps each poll proportional to the live queue, and `per_page: 100` minimizes the remaining API calls while preserving correctness. -So you get a **workflow-local mutation queue** for durable events, plus a -**cross-workflow latest-revision lane** for Commit and Pull Request review on -the same branch. Unrelated branches and workflows do not block each other. +So you get a **workflow-local mutation queue** for durable events, plus +**event-specific latest-revision lanes** for Commit and Pull Request. Open PRs +have one Bugbot owner, and unrelated branches and workflows do not block each +other. ### Example @@ -212,7 +213,7 @@ jobs: project-ids: '2,3' ``` -Durable mutation workflows deliberately do not use a GitHub Actions `concurrency` block: native cancellation retains only one pending run and can discard intermediate issue, comment, or release mutations. The Commit and Pull Request templates are the deliberate exception because their Bugbot result is replaceable by the newer head and every finding mutation is retry-convergent. Native concurrency also remains appropriate for replaceable read-only workflows such as CI and RepoWise. +Durable mutation workflows deliberately do not use a GitHub Actions `concurrency` block: native cancellation retains only one pending run and can discard intermediate issue, comment, or release mutations. The Commit and Pull Request templates are the deliberate exception for their own replaceable branch revisions, but they use distinct groups and cannot cancel one another. Native concurrency also remains appropriate for replaceable read-only workflows such as CI and RepoWise. --- diff --git a/docs/how-to-use.mdx b/docs/how-to-use.mdx index b5b4bbf2e..aead11c3a 100644 --- a/docs/how-to-use.mdx +++ b/docs/how-to-use.mdx @@ -319,7 +319,7 @@ and roles: |------|--------| | `copilot_issue.yml` | Runs on issue events (opened, edited, labeled, unlabeled, etc.): creates branches, links to projects, assignees, deploy trigger. | | `copilot_pull_request.yml` | Runs on PR events: links PR to issue/project, reviewers, AI description, etc. | -| `copilot_commit.yml` | Runs the issue-centric push pipeline (all branches except main/develop by default): commit notice, size/progress, and Bugbot. | +| `copilot_commit.yml` | Runs the issue-centric push pipeline (all branches except main/develop by default): native issue state and size/progress. It also runs Bugbot when the branch has no open PR; otherwise the PR synchronization event owns review. | | `copilot_branch_sync.yml` | Lightweight push observer on all branches: detects parent/working drift and maintains a synchronization notice without loading Bugbot or an agent. | | `copilot_issue_comment.yml` | Runs on explicitly addressed issue comments: e.g. Think action (answer questions). | | `copilot_pull_request_comment.yml` | Runs on explicitly addressed PR review comments, including translation checks. | diff --git a/docs/index.mdx b/docs/index.mdx index 0677d8cf4..e4658d436 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -77,6 +77,6 @@ For a complete list of what the action does (workflow triggers and single action ### Performance - Fast execution -- **Workflow coordination** — Durable mutations queue without discarding intermediate events, while Commit and Pull Request review share a branch-scoped latest-revision lane so stale Bugbot analysis is canceled and safely reconciled (see [Features → Workflow concurrency](/features#workflow-concurrency-and-sequential-execution)). +- **Workflow coordination** — Durable mutations queue without discarding intermediate events. Commit and Pull Request use separate branch-scoped lanes, and an open PR makes its synchronization event the sole Bugbot owner for that head (see [Features → Workflow concurrency](/features#workflow-concurrency-and-sequential-execution)). - Efficient resource utilization - Reliable automation diff --git a/docs/issues/index.mdx b/docs/issues/index.mdx index 6f8bc6ae2..8026dbe08 100644 --- a/docs/issues/index.mdx +++ b/docs/issues/index.mdx @@ -44,7 +44,7 @@ Copilot automates **issue tracking** so that labels, branch creation, project li | Merge the branch (e.g. into develop) | Automatically close the issue when the branch is merged. | | Leave an issue waiting for 7 days (with cleanup enabled) | Automatically close it with an explanation; reopen it with a new comment if needed. | -**Bugbot** (potential problems) runs on **push** (or single action) and posts findings on the issue and on open PRs; you can ask the bot to fix findings from a comment. See [Bugbot](/bugbot) for full details. +**Bugbot** (potential problems) runs on **push** when the issue-linked branch has no open PR, or on a single action. Once a PR is open, its synchronization event owns Bugbot review and the push run keeps issue progress current without duplicating findings. You can ask the bot to fix findings from a comment. See [Bugbot](/bugbot) for full details. ## Lifecycle state labels diff --git a/docs/pull-requests/capabilities.mdx b/docs/pull-requests/capabilities.mdx index 2cbafbe84..ae0a9abfd 100644 --- a/docs/pull-requests/capabilities.mdx +++ b/docs/pull-requests/capabilities.mdx @@ -85,7 +85,7 @@ Semantic PR messages never include decorative images. ## Bugbot on PRs -**Bugbot** (potential problems) reviews the full PR on `opened` and `reopened`. On `synchronize`, it reviews the newly pushed commit range and checks every open finding against current code. It also reviews changes from the **push** workflow or the single action `detect_potential_problems_action`. It posts up to the configured comment limit as **review comments** at validated file/line or file-level anchors and summarizes overflow in the review. Each submitted review remains a historical snapshot; after every mutation Bugbot re-reads GitHub, projects durable per-review facts into generated review status blocks, and projects every accepted current finding—including overflow—into one canonical PR status card, labels, Job Summary, and Check. The review block explicitly flags historical overflow because those entries have no individual thread to reconcile. You can **ask the bot to fix findings** by replying in the thread or commenting on the PR. See [Bugbot](/bugbot). +**Bugbot** (potential problems) reviews the full PR on `opened` and `reopened`. On `synchronize`, the PR workflow exclusively owns Bugbot: it reviews the newly pushed commit range and checks every open finding against current code. The paired **push** workflow still updates issue progress but skips Bugbot after discovering the open PR; push-time Bugbot remains available for issue-linked branches without an open PR. The single action `detect_potential_problems_action` remains available on demand. Bugbot posts up to the configured comment limit as **review comments** at validated file/line or file-level anchors and summarizes overflow in the review. Each submitted review remains a historical snapshot; after every mutation Bugbot re-reads GitHub, projects durable per-review facts into generated review status blocks, and projects every accepted current finding—including overflow—into one canonical PR status card, labels, Job Summary, and Check. The review block explicitly flags historical overflow because those entries have no individual thread to reconcile. You can **ask the bot to fix findings** by replying in the thread or commenting on the PR. See [Bugbot](/bugbot). ## Review state and evidence diff --git a/docs/pull-requests/examples.mdx b/docs/pull-requests/examples.mdx index ccb3d8457..31588389e 100644 --- a/docs/pull-requests/examples.mdx +++ b/docs/pull-requests/examples.mdx @@ -23,7 +23,7 @@ jobs: name: Copilot - Pull Request runs-on: ubuntu-latest concurrency: - group: copilot-bugbot-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }} + group: copilot-pr-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }} cancel-in-progress: ${{ github.event_name != 'pull_request' || github.event.action != 'edited' }} steps: - name: Checkout Repository diff --git a/docs/pull-requests/index.mdx b/docs/pull-requests/index.mdx index b9ae5c213..94a3c3220 100644 --- a/docs/pull-requests/index.mdx +++ b/docs/pull-requests/index.mdx @@ -32,9 +32,9 @@ When your workflow runs on **`pull_request`** events (e.g. opened, edited, label | A **PR is opened or reopened** | Links the PR to its issue and projects, assigns reviewers, applies size/priority policy, optionally generates its description, and runs a full Bugbot review. | | New commits **synchronize** the PR | Optionally refreshes the AI description and reviews only the new commit range while reconciling every open finding. | | PR metadata is **edited** | Normalizes the title without invoking an agent or replaying creation-time mutations. | -| **Push** to the PR branch | Commit workflow runs; Bugbot can post findings as review comments and update/sync with the issue. You can ask the bot to fix findings from a comment. See [Bugbot](/bugbot). | +| **Push** to the PR branch | Commit workflow updates issue progress while the paired PR `synchronize` event exclusively owns Bugbot review for that head. You can ask the bot to fix findings from a comment. See [Bugbot](/bugbot). | -**Bugbot** (potential problems) runs on PR code-change events, on **push** to an issue-linked branch, or on demand. When a PR is available, each analysis publishes one review summary with its new findings attached as inline comments; it does not duplicate those findings as independent PR conversation comments. For full details, see [Bugbot](/bugbot). +**Bugbot** (potential problems) runs on PR code-change events, on **push** to an issue-linked branch that has no open PR, or on demand. When a PR is available, its PR event publishes one review summary with new findings attached as inline comments; it does not duplicate the review from the paired push or publish findings as independent PR conversation comments. For full details, see [Bugbot](/bugbot). ## Next steps diff --git a/docs/pull-requests/workflow-setup.mdx b/docs/pull-requests/workflow-setup.mdx index 7fde330e4..67285aa74 100644 --- a/docs/pull-requests/workflow-setup.mdx +++ b/docs/pull-requests/workflow-setup.mdx @@ -45,7 +45,7 @@ jobs: name: Copilot - Pull Request runs-on: ubuntu-latest concurrency: - group: copilot-bugbot-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }} + group: copilot-pr-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }} cancel-in-progress: ${{ github.event_name != 'pull_request' || github.event.action != 'edited' }} steps: - name: Checkout Repository @@ -74,10 +74,11 @@ jobs: - **`token`** is required (use a fine-grained PAT with repo and project permissions; see [Authentication](/authentication)). - **`project-ids`** is optional but needed if you want PRs linked to GitHub Project boards and moved to columns (e.g. "In Progress"). - The conditional fetch downloads only the two commit objects needed by the incremental reviewer. Keep it when `synchronize` is enabled; a full-history checkout is not required. -- Keep the conditional concurrency expression exactly as shown. A newer code or - review event cancels an obsolete analysis, while a metadata-only `edited` - event waits instead of canceling the active Bugbot review. If several edits - arrive while it waits, GitHub retains only the newest pending edit. +- Keep the PR-specific concurrency group and conditional expression exactly as + shown. A newer PR review event cancels an obsolete PR analysis, while a + metadata-only `edited` event waits instead of canceling the active Bugbot + review. The Commit workflow uses its own `copilot-push-…` group, so a paired + `push` event cannot cancel this workflow (or be canceled by it). - The queued metadata run keeps its own workflow result and Job Summary but does not publish a generic PR comment or `Copilot / Review`. Only a result with exactly one validated, current-schema Bugbot telemetry snapshot for the exact head owns that Check name; partial, skipped, and superseded @@ -102,7 +103,7 @@ jobs: 7. **Conversation UX:** The action publishes only purpose-specific replies, one current Bugbot card, and bounded inline findings. Routine metadata changes do not post a generic action summary, and decorative image inputs are deprecated. See [Capabilities](/pull-requests/capabilities). -8. **Bugbot review:** On `opened` and `reopened`, the reviewer role analyzes the full PR. On `synchronize`, it analyzes the exact new commit range and reconciles every open finding against current code. Metadata-only edits do not invoke the agent. Active findings move the PR to `state:changes-requested` and `state:awaiting-issue-author`; a clean review moves it to `state:ready` and `state:awaiting-maintainer`. During the run, `state:ai-processing` may coexist with those labels and is removed when the agent finishes. Configure `reviewer-provider`, `reviewer-model-provider`, `reviewer-model`, `reviewer-effort`, and `reviewer-executable` only when the reviewer should differ from the base agent. +8. **Bugbot review:** On `opened` and `reopened`, the reviewer role analyzes the full PR. On `synchronize`, this workflow exclusively owns Bugbot for the new commit range and reconciles every open finding against current code; the paired Commit run still updates issue progress but skips its Bugbot pass. Metadata-only edits do not invoke the agent. Active findings move the PR to `state:changes-requested` and `state:awaiting-issue-author`; a clean review moves it to `state:ready` and `state:awaiting-maintainer`. During the run, `state:ai-processing` may coexist with those labels and is removed when the agent finishes. Configure `reviewer-provider`, `reviewer-model-provider`, `reviewer-model`, `reviewer-effort`, and `reviewer-executable` only when the reviewer should differ from the base agent. 9. **External lifecycle evidence:** `pull_request_review` updates only the managed lifecycle label. A requested change marks the PR as changes requested, and an approved review can mark it ready. This event does not invoke an agent pipeline. If you add private workflows for external checks, wire and scope those triggers in your own repository. diff --git a/docs/security-operations/operations/troubleshooting.mdx b/docs/security-operations/operations/troubleshooting.mdx index ba5873b08..d29b1f174 100644 --- a/docs/security-operations/operations/troubleshooting.mdx +++ b/docs/security-operations/operations/troubleshooting.mdx @@ -77,7 +77,7 @@ This guide helps you resolve common issues you might encounter while using Copil external GitHub wait and has no release runner to extend. npm visibility has its own bounded polling interval and timeout. 2. **Network:** Check GitHub API status, verify network access, and monitor rate limits. - 3. **Triggers:** Verify event triggers, workflow conditions, and concurrent executions. A newer code-change review may intentionally cancel an older one. A metadata-only `pull_request: edited` run must wait and must not cancel an active `synchronize` review; after it runs, it must not create a generic PR comment or `Copilot / Review`. If it cancels the review, appends lifecycle/debug-only comments, or a newer same-name Check says `Bugbot review: —`, update the shipped workflows and Action before trusting the green metadata run. Bounded partial, skipped, and superseded reviews should be neutral, not successful. + 3. **Triggers:** Verify event triggers, workflow conditions, and concurrent executions. A newer run may intentionally cancel an older run from the same workflow, but a Commit run and its paired Pull Request run must not cancel one another. When an open same-repository PR exists, Commit must skip Bugbot and `pull_request:synchronize` must own review for that head. A metadata-only `pull_request: edited` run must wait and must not cancel an active `synchronize` review; after it runs, it must not create a generic PR comment or `Copilot / Review`. If event owners cross-cancel, both run Bugbot for one head, append lifecycle/debug-only comments, or a newer same-name Check says `Bugbot review: —`, update the shipped workflows and Action before trusting the result. Bounded partial, skipped, and superseded reviews should be neutral, not successful. diff --git a/scripts/validate-workflow-contract.cjs b/scripts/validate-workflow-contract.cjs index 9d3cba1da..a7a2e6d3a 100644 --- a/scripts/validate-workflow-contract.cjs +++ b/scripts/validate-workflow-contract.cjs @@ -24,12 +24,18 @@ const DEPLOYMENT_CONTINUATION_CONCURRENCY_GROUP = 'copilot-deployment-${{ github const DISTRIBUTED_COPILOT_ACTION = 'vypdev/copilot@v3'; const CHECKOUT_ACTION = 'actions/checkout@v5'; const SETUP_NODE_ACTION = 'actions/setup-node@v7'; -const BUGBOT_BRANCH_CONCURRENCY_GROUP = 'copilot-bugbot-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}'; +const PUSH_BRANCH_CONCURRENCY_GROUP = 'copilot-push-${{ github.repository }}-${{ github.ref_name }}'; +const PULL_REQUEST_BRANCH_CONCURRENCY_GROUP = 'copilot-pr-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}'; const BUGBOT_PULL_REQUEST_CANCEL_EXPRESSION = "${{ github.event_name != 'pull_request' || github.event.action != 'edited' }}"; const BUGBOT_CONCURRENCY_JOBS = Object.freeze({ - 'copilot_commit.yml': Object.freeze({ jobId: 'copilot-commits', cancelInProgress: true }), + 'copilot_commit.yml': Object.freeze({ + jobId: 'copilot-commits', + group: PUSH_BRANCH_CONCURRENCY_GROUP, + cancelInProgress: true, + }), 'copilot_pull_request.yml': Object.freeze({ jobId: 'copilot-pull-requests', + group: PULL_REQUEST_BRANCH_CONCURRENCY_GROUP, cancelInProgress: BUGBOT_PULL_REQUEST_CANCEL_EXPRESSION, }), }); @@ -313,7 +319,7 @@ function assertReviewConcurrency(relativeFile, workflow) { assertNoConcurrency(relativeFile, workflow); return; } - const { jobId: targetJobId, cancelInProgress } = contract; + const { jobId: targetJobId, group, cancelInProgress } = contract; if (workflow.concurrency !== undefined) { throw new Error(`${relativeFile} must scope Bugbot branch concurrency to job ${targetJobId}.`); } @@ -324,9 +330,9 @@ function assertReviewConcurrency(relativeFile, workflow) { } continue; } - if (job.concurrency?.group !== BUGBOT_BRANCH_CONCURRENCY_GROUP + if (job.concurrency?.group !== group || job.concurrency?.['cancel-in-progress'] !== cancelInProgress) { - throw new Error(`${relativeFile} job ${jobId} must share the Bugbot branch group, cancel superseded code-review runs, and queue pull_request edited events without preempting an active review.`); + throw new Error(`${relativeFile} job ${jobId} must use its workflow-specific branch group, avoid cross-canceling the other event owner, cancel superseded runs, and queue pull_request edited events without preempting an active review.`); } } } diff --git a/setup/workflows/copilot_commit.yml b/setup/workflows/copilot_commit.yml index d296142f0..97e4daa61 100644 --- a/setup/workflows/copilot_commit.yml +++ b/setup/workflows/copilot_commit.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 120 concurrency: - group: copilot-bugbot-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }} + group: copilot-push-${{ github.repository }}-${{ github.ref_name }} cancel-in-progress: true permissions: contents: read diff --git a/setup/workflows/copilot_pull_request.yml b/setup/workflows/copilot_pull_request.yml index f2be1075f..9fde6bb29 100644 --- a/setup/workflows/copilot_pull_request.yml +++ b/setup/workflows/copilot_pull_request.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 120 concurrency: - group: copilot-bugbot-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }} + group: copilot-pr-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }} cancel-in-progress: ${{ github.event_name != 'pull_request' || github.event.action != 'edited' }} permissions: checks: write diff --git a/specs/CATALOG.md b/specs/CATALOG.md index 8901a3e1a..21ca4affd 100644 --- a/specs/CATALOG.md +++ b/specs/CATALOG.md @@ -13,9 +13,9 @@ debt or convert unknown historic intent into a design decision. | `github-communication-experience` | Proposed | English-default, localized, semantic, bounded, and idempotent product messages across GitHub and repository-aware operator surfaces | [Semantic GitHub communication and repository localization](./semantic-github-publication-and-notification.md) + 1 companion | 75 paths · 2026-09-14 | | `release-orchestration` | Implemented | Release and hotfix promotion, publication, reconciliation, and durable recovery | [Configurable production-first release orchestration](./configurable-release-orchestration.md) + 2 companion | 41 paths · 2026-09-12 | | `merge-queue-readiness` | Implemented | Fail-closed validation of required checks and merge-group workflow support | [Merge queue readiness and effective target rules](./merge-queue-readiness.md) | 15 paths · 2026-09-11 | -| `bugbot-review-state-reconciliation` | Implemented | Reconcile review snapshots, findings, threads, comments, and check conclusions | [Bugbot review-state reconciliation](./bugbot-review-state-reconciliation.md) | 39 paths · 2026-09-13 | +| `bugbot-review-state-reconciliation` | Implemented | Reconcile review snapshots, findings, threads, comments, and check conclusions | [Bugbot review-state reconciliation](./bugbot-review-state-reconciliation.md) | 42 paths · 2026-09-14 | | `execution-lifecycle` | Implemented | Shared GitHub Action lifecycle from event admission through durable user-facing results | [Execution admission, queueing, routing, and result publication](./execution-admission-queue-and-publication.md) + 3 companion | 82 paths · 2026-09-14 | -| `architecture-quality-hardening` | Implemented | Close verified concurrency, error-contract, context-coupling, fan-out, setup/doctor, and provider-policy risks in dependency order | [Architecture quality and scalability hardening](./architecture-quality-and-scalability-hardening.md) + 1 companion | 70 paths · 2026-09-14 | +| `architecture-quality-hardening` | Implemented | Close verified concurrency, error-contract, context-coupling, fan-out, setup/doctor, and provider-policy risks in dependency order | [Architecture quality and scalability hardening](./architecture-quality-and-scalability-hardening.md) + 1 companion | 71 paths · 2026-09-14 | | `setup-and-doctor` | Implemented | Plan, validate, provision, and audit a repository installation without exposing credentials | [Setup, configuration, credentials, and doctor](./setup-configuration-credentials-and-doctor.md) + 1 companion | 39 paths · 2026-09-12 | | `managed-issue-lifecycle` | As-built baseline | Convert typed issues into traceable work branches, project state, and lifecycle state | [Managed issue and branch lifecycle](./managed-issue-and-branch-lifecycle.md) | 21 paths · 2026-09-13 | | `comment-automation` | Implemented | Admit only explicit commands or exact mentions, then route them while protecting repository mutations | [Comment automation and authorization](./comment-automation-and-authorization.md) | 46 paths · 2026-09-13 | @@ -63,12 +63,12 @@ debt or convert unknown historic intent into a design decision. ### `bugbot-review-state-reconciliation` — Bugbot review-state reconciliation - Owner: Copilot maintainers -- Last verified: 2026-09-13 +- Last verified: 2026-09-14 - Specifications: [`specs/bugbot-review-state-reconciliation.md`](./bugbot-review-state-reconciliation.md) -- Workflows: [`.github/workflows/copilot_pull_request.yml`](../.github/workflows/copilot_pull_request.yml) · [`.github/workflows/copilot_pull_request_comment.yml`](../.github/workflows/copilot_pull_request_comment.yml) -- Entrypoints: [`src/application/usecases/steps/commit/bugbot/reconcile_bugbot_review_state_use_case.ts`](../src/application/usecases/steps/commit/bugbot/reconcile_bugbot_review_state_use_case.ts) · [`src/actions/github_action_completion.ts`](../src/actions/github_action_completion.ts) +- Workflows: [`.github/workflows/copilot_commit.yml`](../.github/workflows/copilot_commit.yml) · [`.github/workflows/copilot_pull_request.yml`](../.github/workflows/copilot_pull_request.yml) · [`.github/workflows/copilot_pull_request_comment.yml`](../.github/workflows/copilot_pull_request_comment.yml) +- Entrypoints: [`src/application/usecases/commit_use_case.ts`](../src/application/usecases/commit_use_case.ts) · [`src/application/usecases/steps/commit/bugbot/reconcile_bugbot_review_state_use_case.ts`](../src/application/usecases/steps/commit/bugbot/reconcile_bugbot_review_state_use_case.ts) · [`src/actions/github_action_completion.ts`](../src/actions/github_action_completion.ts) - Core code: [`src/domain/bugbot/review_state.ts`](../src/domain/bugbot/review_state.ts) · [`src/domain/bugbot/review_projection.ts`](../src/domain/bugbot/review_projection.ts) · [`src/application/policies/bugbot_reconciliation_policy.ts`](../src/application/policies/bugbot_reconciliation_policy.ts) · [`src/application/policies/bugbot_result_finding_state_projection_policy.ts`](../src/application/policies/bugbot_result_finding_state_projection_policy.ts) · [`src/application/policies/bugbot_telemetry_projection_policy.ts`](../src/application/policies/bugbot_telemetry_projection_policy.ts) · [`src/application/policies/action_summary_policy.ts`](../src/application/policies/action_summary_policy.ts) · [`src/application/policies/copilot_evidence_policy.ts`](../src/application/policies/copilot_evidence_policy.ts) · [`src/application/policies/lifecycle_state_policy.ts`](../src/application/policies/lifecycle_state_policy.ts) · [`src/application/policies/status_command_policy.ts`](../src/application/policies/status_command_policy.ts) · [`src/application/usecases/steps/common/publish_resume_workflow.ts`](../src/application/usecases/steps/common/publish_resume_workflow.ts) · [`src/application/usecases/steps/commit/bugbot/schema.ts`](../src/application/usecases/steps/commit/bugbot/schema.ts) · [`src/application/usecases/steps/commit/bugbot/prepare_bugbot_findings_policy.ts`](../src/application/usecases/steps/commit/bugbot/prepare_bugbot_findings_policy.ts) · [`src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts`](../src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts) -- Tests: [`src/domain/bugbot/__tests__/review_state.test.ts`](../src/domain/bugbot/__tests__/review_state.test.ts) · [`src/domain/bugbot/__tests__/review_projection.test.ts`](../src/domain/bugbot/__tests__/review_projection.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/schema.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/schema.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/prepare_bugbot_findings.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/prepare_bugbot_findings.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/reconcile_bugbot_review_state_integration.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/reconcile_bugbot_review_state_integration.test.ts) · [`src/application/policies/__tests__/bugbot_result_finding_state_projection_policy.test.ts`](../src/application/policies/__tests__/bugbot_result_finding_state_projection_policy.test.ts) · [`src/application/policies/__tests__/bugbot_telemetry_projection_policy.test.ts`](../src/application/policies/__tests__/bugbot_telemetry_projection_policy.test.ts) · [`src/application/policies/__tests__/action_summary_policy.test.ts`](../src/application/policies/__tests__/action_summary_policy.test.ts) · [`src/application/policies/__tests__/copilot_evidence_policy.test.ts`](../src/application/policies/__tests__/copilot_evidence_policy.test.ts) · [`src/application/policies/__tests__/lifecycle_state_policy.test.ts`](../src/application/policies/__tests__/lifecycle_state_policy.test.ts) · [`src/application/policies/__tests__/status_command_policy.test.ts`](../src/application/policies/__tests__/status_command_policy.test.ts) · [`src/actions/__tests__/github_action_completion.test.ts`](../src/actions/__tests__/github_action_completion.test.ts) · [`src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts`](../src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts) · [`src/application/usecases/actions/__tests__/synchronize_lifecycle_state_use_case.test.ts`](../src/application/usecases/actions/__tests__/synchronize_lifecycle_state_use_case.test.ts) · [`src/tooling/__tests__/validate_workflow_contract.test.ts`](../src/tooling/__tests__/validate_workflow_contract.test.ts) +- Tests: [`src/application/usecases/__tests__/commit_use_case.test.ts`](../src/application/usecases/__tests__/commit_use_case.test.ts) · [`src/domain/bugbot/__tests__/review_state.test.ts`](../src/domain/bugbot/__tests__/review_state.test.ts) · [`src/domain/bugbot/__tests__/review_projection.test.ts`](../src/domain/bugbot/__tests__/review_projection.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/schema.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/schema.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/prepare_bugbot_findings.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/prepare_bugbot_findings.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/reconcile_bugbot_review_state_integration.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/reconcile_bugbot_review_state_integration.test.ts) · [`src/application/policies/__tests__/bugbot_result_finding_state_projection_policy.test.ts`](../src/application/policies/__tests__/bugbot_result_finding_state_projection_policy.test.ts) · [`src/application/policies/__tests__/bugbot_telemetry_projection_policy.test.ts`](../src/application/policies/__tests__/bugbot_telemetry_projection_policy.test.ts) · [`src/application/policies/__tests__/action_summary_policy.test.ts`](../src/application/policies/__tests__/action_summary_policy.test.ts) · [`src/application/policies/__tests__/copilot_evidence_policy.test.ts`](../src/application/policies/__tests__/copilot_evidence_policy.test.ts) · [`src/application/policies/__tests__/lifecycle_state_policy.test.ts`](../src/application/policies/__tests__/lifecycle_state_policy.test.ts) · [`src/application/policies/__tests__/status_command_policy.test.ts`](../src/application/policies/__tests__/status_command_policy.test.ts) · [`src/actions/__tests__/github_action_completion.test.ts`](../src/actions/__tests__/github_action_completion.test.ts) · [`src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts`](../src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts) · [`src/application/usecases/actions/__tests__/synchronize_lifecycle_state_use_case.test.ts`](../src/application/usecases/actions/__tests__/synchronize_lifecycle_state_use_case.test.ts) · [`src/tooling/__tests__/validate_workflow_contract.test.ts`](../src/tooling/__tests__/validate_workflow_contract.test.ts) - User documentation: [`docs/bugbot/detection.mdx`](../docs/bugbot/detection.mdx) · [`docs/bugbot/finding-publication.mdx`](../docs/bugbot/finding-publication.mdx) · [`docs/bugbot/quality-observability.mdx`](../docs/bugbot/quality-observability.mdx) · [`docs/bugbot/failure-scenarios.mdx`](../docs/bugbot/failure-scenarios.mdx) · [`docs/bugbot/configuration.mdx`](../docs/bugbot/configuration.mdx) · [`docs/bugbot/how-it-works.mdx`](../docs/bugbot/how-it-works.mdx) · [`docs/pull-requests/workflow-setup.mdx`](../docs/pull-requests/workflow-setup.mdx) ### `execution-lifecycle` — Execution admission, queueing, routing, and result publication @@ -87,7 +87,7 @@ debt or convert unknown historic intent into a design decision. - Owner: Copilot maintainers - Last verified: 2026-09-14 - Specifications: [`specs/architecture-quality-and-scalability-hardening.md`](./architecture-quality-and-scalability-hardening.md) · [`specs/issue-and-pull-request-context-hardening.md`](./issue-and-pull-request-context-hardening.md) -- Workflows: [`.github/workflows/copilot_pull_request.yml`](../.github/workflows/copilot_pull_request.yml) · [`.github/workflows/release_workflow.yml`](../.github/workflows/release_workflow.yml) · [`.github/workflows/hotfix_workflow.yml`](../.github/workflows/hotfix_workflow.yml) · [`.github/workflows/copilot_deployment_orchestration.yml`](../.github/workflows/copilot_deployment_orchestration.yml) +- Workflows: [`.github/workflows/copilot_commit.yml`](../.github/workflows/copilot_commit.yml) · [`.github/workflows/copilot_pull_request.yml`](../.github/workflows/copilot_pull_request.yml) · [`.github/workflows/release_workflow.yml`](../.github/workflows/release_workflow.yml) · [`.github/workflows/hotfix_workflow.yml`](../.github/workflows/hotfix_workflow.yml) · [`.github/workflows/copilot_deployment_orchestration.yml`](../.github/workflows/copilot_deployment_orchestration.yml) - Entrypoints: [`src/actions/github_action.ts`](../src/actions/github_action.ts) · [`src/cli/commands/setup.ts`](../src/cli/commands/setup.ts) · [`src/cli/commands/doctor.ts`](../src/cli/commands/doctor.ts) - Core code: [`scripts/coverage-budgets.json`](../scripts/coverage-budgets.json) · [`scripts/validate-coverage-budgets.cjs`](../scripts/validate-coverage-budgets.cjs) · [`src/application/errors/application_error.ts`](../src/application/errors/application_error.ts) · [`src/application/errors/application_error_context.ts`](../src/application/errors/application_error_context.ts) · [`src/application/policies/application_error_presentation_policy.ts`](../src/application/policies/application_error_presentation_policy.ts) · [`src/application/policies/bugbot_result_finding_state_projection_policy.ts`](../src/application/policies/bugbot_result_finding_state_projection_policy.ts) · [`src/application/policies/bugbot_telemetry_projection_policy.ts`](../src/application/policies/bugbot_telemetry_projection_policy.ts) · [`src/application/policies/action_summary_policy.ts`](../src/application/policies/action_summary_policy.ts) · [`src/application/policies/copilot_evidence_policy.ts`](../src/application/policies/copilot_evidence_policy.ts) · [`src/architecture/execution_import_baseline.json`](../src/architecture/execution_import_baseline.json) · [`src/api.ts`](../src/api.ts) · [`src/data/model/execution.ts`](../src/data/model/execution.ts) · [`src/application/usecases/actions/deployment_orchestration_use_case.ts`](../src/application/usecases/actions/deployment_orchestration_use_case.ts) · [`src/application/usecases/steps/commit/bugbot/load_bugbot_context_use_case.ts`](../src/application/usecases/steps/commit/bugbot/load_bugbot_context_use_case.ts) · [`src/application/usecases/steps/commit/bugbot/bugbot_review_operation_context.ts`](../src/application/usecases/steps/commit/bugbot/bugbot_review_operation_context.ts) · [`src/application/usecases/comment_automation_context.ts`](../src/application/usecases/comment_automation_context.ts) · [`src/application/usecases/actions/lifecycle_synchronization_context.ts`](../src/application/usecases/actions/lifecycle_synchronization_context.ts) · [`src/application/usecases/actions/synchronize_lifecycle_state_use_case.ts`](../src/application/usecases/actions/synchronize_lifecycle_state_use_case.ts) · [`src/application/usecases/issue_workflow_context.ts`](../src/application/usecases/issue_workflow_context.ts) · [`src/application/usecases/pull_request_workflow_context.ts`](../src/application/usecases/pull_request_workflow_context.ts) · [`src/application/usecases/steps/common/publish_resume_workflow.ts`](../src/application/usecases/steps/common/publish_resume_workflow.ts) · [`src/application/usecases/steps/common/store_configuration_use_case.ts`](../src/application/usecases/steps/common/store_configuration_use_case.ts) · [`src/infrastructure/composition/shared_capability_port_binding.ts`](../src/infrastructure/composition/shared_capability_port_binding.ts) · [`src/infrastructure/composition/lifecycle_capability_port_binding.ts`](../src/infrastructure/composition/lifecycle_capability_port_binding.ts) · [`src/infrastructure/composition/lifecycle_state_composition_root.ts`](../src/infrastructure/composition/lifecycle_state_composition_root.ts) · [`src/application/usecases/setup/doctor_use_case.ts`](../src/application/usecases/setup/doctor_use_case.ts) · [`src/application/policies/setup_questionnaire_policy.ts`](../src/application/policies/setup_questionnaire_policy.ts) · [`src/cli/setup_terminal_driver.ts`](../src/cli/setup_terminal_driver.ts) · [`src/application/policies/agent_execution/agent_execution_policy_dispatcher.ts`](../src/application/policies/agent_execution/agent_execution_policy_dispatcher.ts) - Tests: [`src/tooling/__tests__/coverage_budget_validator.test.ts`](../src/tooling/__tests__/coverage_budget_validator.test.ts) · [`src/__tests__/api.test.ts`](../src/__tests__/api.test.ts) · [`src/application/errors/__tests__/application_error.test.ts`](../src/application/errors/__tests__/application_error.test.ts) · [`src/application/policies/__tests__/application_error_presentation_policy.test.ts`](../src/application/policies/__tests__/application_error_presentation_policy.test.ts) · [`src/application/policies/__tests__/bugbot_result_finding_state_projection_policy.test.ts`](../src/application/policies/__tests__/bugbot_result_finding_state_projection_policy.test.ts) · [`src/application/policies/__tests__/bugbot_telemetry_projection_policy.test.ts`](../src/application/policies/__tests__/bugbot_telemetry_projection_policy.test.ts) · [`src/application/policies/__tests__/action_summary_policy.test.ts`](../src/application/policies/__tests__/action_summary_policy.test.ts) · [`src/application/policies/__tests__/copilot_evidence_policy.test.ts`](../src/application/policies/__tests__/copilot_evidence_policy.test.ts) · [`src/application/usecases/actions/__tests__/lifecycle_synchronization_context.test.ts`](../src/application/usecases/actions/__tests__/lifecycle_synchronization_context.test.ts) · [`src/application/usecases/actions/__tests__/synchronize_lifecycle_state_use_case.test.ts`](../src/application/usecases/actions/__tests__/synchronize_lifecycle_state_use_case.test.ts) · [`src/application/usecases/actions/__tests__/lifecycle_event_replay.integration.test.ts`](../src/application/usecases/actions/__tests__/lifecycle_event_replay.integration.test.ts) · [`src/actions/__tests__/github_action_completion.test.ts`](../src/actions/__tests__/github_action_completion.test.ts) · [`src/architecture/__tests__/execution_import_ratchet.test.ts`](../src/architecture/__tests__/execution_import_ratchet.test.ts) · [`src/architecture/__tests__/raw_error_logging_boundaries.test.ts`](../src/architecture/__tests__/raw_error_logging_boundaries.test.ts) · [`src/application/usecases/actions/__tests__/deployment_orchestration_use_case.test.ts`](../src/application/usecases/actions/__tests__/deployment_orchestration_use_case.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/load_bugbot_context_use_case.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/load_bugbot_context_use_case.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/bugbot_review_operation_context.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/bugbot_review_operation_context.test.ts) · [`src/application/usecases/steps/common/__tests__/shared_capability_context_projection.test.ts`](../src/application/usecases/steps/common/__tests__/shared_capability_context_projection.test.ts) · [`src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts`](../src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts) · [`src/infrastructure/composition/__tests__/shared_capability_port_binding.test.ts`](../src/infrastructure/composition/__tests__/shared_capability_port_binding.test.ts) · [`src/application/usecases/__tests__/issue_pull_request_context_projection.test.ts`](../src/application/usecases/__tests__/issue_pull_request_context_projection.test.ts) · [`src/infrastructure/composition/__tests__/lifecycle_capability_port_binding.test.ts`](../src/infrastructure/composition/__tests__/lifecycle_capability_port_binding.test.ts) · [`src/infrastructure/composition/__tests__/lifecycle_state_composition_root.test.ts`](../src/infrastructure/composition/__tests__/lifecycle_state_composition_root.test.ts) · [`src/application/usecases/setup/__tests__/doctor_use_case.test.ts`](../src/application/usecases/setup/__tests__/doctor_use_case.test.ts) · [`src/application/policies/__tests__/setup_questionnaire_policy.test.ts`](../src/application/policies/__tests__/setup_questionnaire_policy.test.ts) · [`src/application/policies/__tests__/agent_execution_plan_policy.test.ts`](../src/application/policies/__tests__/agent_execution_plan_policy.test.ts) · [`src/tooling/__tests__/validate_workflow_contract.test.ts`](../src/tooling/__tests__/validate_workflow_contract.test.ts) diff --git a/specs/bugbot-review-state-reconciliation.md b/specs/bugbot-review-state-reconciliation.md index 20beec891..9db16c8a3 100644 --- a/specs/bugbot-review-state-reconciliation.md +++ b/specs/bugbot-review-state-reconciliation.md @@ -150,7 +150,7 @@ review summary does not implement. reusable Action therefore cannot promise an immediate run when a person only resolves or reopens a native thread. -### 2.6 Verified concurrency regression on PR #363 +### 2.6 Verified concurrency regressions and ownership correction On three successive PR heads, an `efraespada`-authenticated metadata update emitted `pull_request: edited` while the `synchronize` review was starting. @@ -161,9 +161,9 @@ Sync did not edit the PR in those runs, and bot-authored edits were already filtered. The final Check summary exposed `Bugbot review: —`, proving that a green job was not evidence that the head had been reviewed. -The correction keeps the shared branch group but makes cancellation -conditional. Code-change and review events cancel obsolete analysis; -`pull_request: edited` waits behind the active review. GitHub's bounded pending +The first correction kept the shared branch group but made cancellation +conditional. Code-change and review events canceled obsolete analysis; +`pull_request: edited` waited behind the active review. GitHub's bounded pending slot may replace an older waiting metadata event with the newest one, which is safe because metadata normalization is idempotent and only the newest event is current. Controlled verification on head `70585d9e` started review run @@ -171,6 +171,17 @@ current. Controlled verification on head `70585d9e` started review run review completed successfully, then completed itself. Bot-authored follow-ups `34756389864` and `34756489001` were skipped by the existing loop guard. +PR #367 exposed the remaining flaw: a paired `pull_request:synchronize` run +canceled Commit run `34846885692` through the same native group after every job +step had succeeded, leaving a misleading canceled conclusion. Running Bugbot +from both event routes also made cancellation necessary only because ownership +was duplicated. The final contract therefore uses distinct `copilot-push-…` +and `copilot-pr-…` branch groups. When Commit discovers an open same-repository +PR, it retains issue progress work but skips Bugbot; the PR synchronization +event exclusively owns review for that head. PR metadata retains conditional +non-preemption within the PR-specific group, and fork PR execution remains +excluded by the same-repository workflow gate. + The next head `0e039ae9` proved that ordering alone was insufficient. Review run `34756692307` published Check `103722773263` with truthful `partial` Bugbot telemetry. After a maintainer body edit, metadata-only run `34756981026` @@ -458,13 +469,14 @@ readiness. `unknown` is a system failure and fails the review regardless of updated again. - A run whose analyzed head is no longer the PR head MUST return superseded and MUST NOT mutate findings or current-state projections. -- Shipped code-change workflows MUST share a branch-scoped concurrency group. - Push, PR code-change, and review events use cancel-in-progress semantics so - newer evidence supersedes older analysis. `pull_request: edited` uses the - same group with cancellation disabled, so it waits and cannot preempt an - active review. Application freshness checks remain mandatory because API - consumers and comment-triggered flows are not fully serialized by workflow - YAML. +- Shipped Commit and Pull Request workflows MUST use distinct branch-scoped + concurrency groups. Each uses cancel-in-progress semantics only for its own + replaceable revisions. After Commit discovers an open same-repository PR, it + skips Bugbot and the PR code-change event exclusively owns review for that + head. `pull_request: edited` uses the PR group with cancellation disabled, so + it waits and cannot preempt an active review. Application freshness checks + remain mandatory because API consumers and comment-triggered flows are not + fully serialized by workflow YAML. - If cancellation happens after a durable mutation, the next run discovers the marker/thread mismatch and converges. The status card still names the last completely verified head, so it never turns a canceled attempt into success. @@ -1310,7 +1322,7 @@ examples should reuse the same fixtures as presentation tests where practical. | Safe resolution/reopen order | reconciliation plan/apply use case | every partial mutation boundary | How it works, Failure scenarios | | Human dismissal precedence | lifecycle policy + resolver adapter | bot/human/unknown resolver matrix | Concepts, Detection | | Freshness and concurrency | head guards + workflow contract | stale, duplicate, canceled, race cases | Workflow setup | -| Metadata non-preemption | shared branch key + conditional cancellation | active/setup workflow parser, negative unconditional-cancel fixture, PR #363 live evidence | Workflow setup, Configuration, How it works | +| Review ownership and metadata non-preemption | distinct push/PR branch groups + open-PR Bugbot omission + conditional PR cancellation | active/setup workflow parser, cross-group negative fixtures, Commit route test, PR #363/#367 live evidence | Workflow setup, Configuration, How it works | | Metadata/review publication ownership | result-publication mode + telemetry/evidence policies | metadata generic-comment negative, outcome matrix, completion integration, PR #363 latest-by-name/noise replay | Detection, Workflow setup, How it works, Troubleshooting | | Canonical Result evidence | discriminated telemetry-set and finding-state projections + completion/lifecycle/status/summary/Check policies | complete aggregation, malformed/duplicate telemetry siblings, required-outcome absence, missing/extra key, numeric limits, overflow, cross-surface fail-closed cases | Detection, Observability, Comment commands, Failure scenarios | | Coherent final snapshot | snapshot loader + explicit surface completeness | before/after head, head-change, missing-head, per-surface failure, shared issue/PR read cases | How it works, Failure scenarios | @@ -1383,8 +1395,9 @@ goldens pass in both locales. ### Phase 6 — Workflow and integration hardening -1. Add shared branch concurrency to shipped PR/commit workflows and setup copies; - keep code/review cancellation while queueing PR metadata edits. +1. Add distinct branch concurrency to shipped PR/commit workflows and setup + copies; cancel only superseded same-owner runs, queue PR metadata edits, and + make PR synchronization the sole Bugbot owner once an open PR exists. 2. Reserve `Copilot / Review` for valid Bugbot telemetry and make incomplete review outcomes neutral; metadata-only completion keeps only workflow/summary evidence. diff --git a/specs/catalog.json b/specs/catalog.json index dd550f230..d42329f0a 100644 --- a/specs/catalog.json +++ b/specs/catalog.json @@ -204,15 +204,17 @@ "status": "implemented", "scope": "Reconcile review snapshots, findings, threads, comments, and check conclusions", "owner": "Copilot maintainers", - "lastVerified": "2026-09-13", + "lastVerified": "2026-09-14", "specs": [ "specs/bugbot-review-state-reconciliation.md" ], "workflows": [ + ".github/workflows/copilot_commit.yml", ".github/workflows/copilot_pull_request.yml", ".github/workflows/copilot_pull_request_comment.yml" ], "entrypoints": [ + "src/application/usecases/commit_use_case.ts", "src/application/usecases/steps/commit/bugbot/reconcile_bugbot_review_state_use_case.ts", "src/actions/github_action_completion.ts" ], @@ -232,6 +234,7 @@ "src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts" ], "tests": [ + "src/application/usecases/__tests__/commit_use_case.test.ts", "src/domain/bugbot/__tests__/review_state.test.ts", "src/domain/bugbot/__tests__/review_projection.test.ts", "src/application/usecases/steps/commit/bugbot/__tests__/schema.test.ts", @@ -376,6 +379,7 @@ "specs/issue-and-pull-request-context-hardening.md" ], "workflows": [ + ".github/workflows/copilot_commit.yml", ".github/workflows/copilot_pull_request.yml", ".github/workflows/release_workflow.yml", ".github/workflows/hotfix_workflow.yml", diff --git a/specs/issue-and-pull-request-context-hardening.md b/specs/issue-and-pull-request-context-hardening.md index 73b14edd9..bd59296e5 100644 --- a/specs/issue-and-pull-request-context-hardening.md +++ b/specs/issue-and-pull-request-context-hardening.md @@ -29,10 +29,13 @@ requests. That operation MUST use a repository-owned URL/query, a durable hidden marker, ordered compensation, and truthful partial-state results. Event-provided URLs MUST never be fetched. -PR metadata normalization MUST share the branch serialization boundary without -preempting an active code review. A newer code-change or review event MAY cancel -an obsolete review; `pull_request: edited` MUST wait and MUST NOT replace a real -`synchronize` analysis with a green metadata-only run. After it waits, the +PR metadata normalization MUST share the PR-specific branch serialization +boundary without preempting an active code review. Commit uses a distinct +push-specific boundary, and once it discovers an open same-repository PR it +MUST skip Bugbot so PR synchronization exclusively owns review for that head. +A newer PR review event MAY cancel an obsolete PR review; +`pull_request: edited` MUST wait and MUST NOT replace a real `synchronize` +analysis with a green metadata-only run. After it waits, the metadata-only run MUST publish its own workflow outcome and Job Summary without creating a generic result comment or a newer same-name `Copilot / Review` Check. That Check name is reserved for a result carrying exactly one current-schema @@ -654,6 +657,9 @@ SDDs, catalog metadata, generated catalog, and bundles are updated together. 19. Given Bugbot reports bounded `partial`, `skipped`, or `superseded` telemetry, the Review Check is neutral and names that outcome; it never claims success or whole-PR cleanliness. +20. Given a push and PR synchronization are emitted for the same head, they use + distinct native queues; Commit retains issue progress but skips Bugbot, and + the PR synchronization event owns exactly one review. ## 17. Requirements traceability @@ -666,7 +672,7 @@ SDDs, catalog metadata, generated catalog, and bundles are updated together. | safe PR link | exact-target adapter and compensation workflow | forged URL, replay, marker, every failure edge | PR capabilities, troubleshooting | | clean cut and ceiling | AST ratchet, typecheck, generated bundles | zero-leaf/old-symbol negative fixtures | semantic context SDD | | UX/operations | semantic result strings and common publisher | state/retained-action assertions + manual review | PR and troubleshooting pages | -| review-preserving concurrency | shared branch group + conditional cancellation | workflow validator, negative fixture, PR #363 live sequence | workflow setup, Bugbot configuration/how-it-works | +| review-preserving ownership | distinct push/PR branch groups + conditional PR cancellation + open-PR Bugbot omission | workflow validator, cross-group negative fixtures, Commit route test, PR #363/#367 live sequence | workflow setup, Bugbot configuration/how-it-works | | review publication ownership | result-publication mode + discriminated Bugbot telemetry/evidence policies | metadata-only comment/Check negatives, malformed-sibling cross-consumer cases, partial/complete/skipped policy cases, action-completion integration, PR #363 latest-by-name/noise sequence | workflow setup, Bugbot detection/how-it-works, troubleshooting | ## 18. Implementation sequence @@ -734,10 +740,13 @@ SDDs, catalog metadata, generated catalog, and bundles are updated together. best-effort cleanup. - Decision: one description request with an explicit trigger replaces two entry methods; rejected overloads and deprecated aliases. -- Decision: retain one shared branch concurrency key but conditionally disable - preemption for `pull_request: edited`; rejected unconditional cancellation - because PR #363 proved it can hide a missed review, and rejected parallel - metadata/review lanes because both can mutate the same PR surfaces. +- Decision: use distinct push and PR branch concurrency keys, make PR + synchronization the sole Bugbot owner after an open same-repository PR is + discovered, and conditionally disable PR-key preemption for + `pull_request: edited`. PR #363 proved unconditional metadata cancellation can + hide a missed review; PR #367 proved the shared push/PR key can cancel useful + completed push work. Separate event lanes are safe because only the PR lane + mutates Bugbot PR surfaces once a PR exists. - Decision: reserve `Copilot / Review` for structurally valid Bugbot telemetry and skip metadata-only evidence instead of reading/merging prior Checks; rejected a second metadata Check with the same name and a provider read/write diff --git a/specs/semantic-github-publication-and-notification.md b/specs/semantic-github-publication-and-notification.md index 6d5b1b812..52896b10a 100644 --- a/specs/semantic-github-publication-and-notification.md +++ b/specs/semantic-github-publication-and-notification.md @@ -379,6 +379,21 @@ The shared marker format is: queue confirms no newer conflicting run; otherwise they resolve to `none` with reason `stale-source`. +### 6.5.1 Push and pull-request review ownership + +The Commit and Pull Request workflows MUST use distinct native concurrency +groups keyed by repository and branch. A paired `push` and +`pull_request:synchronize` event MUST NOT cancel one another. Each group MAY +cancel only an older replaceable run from the same workflow; PR metadata-only +`edited` events MUST continue to queue without preempting an active PR review. + +After the Commit route discovers an open same-repository PR for its branch, it +MUST retain native issue state, size, and progress work but MUST omit Bugbot. +The PR `synchronize` route then exclusively owns Bugbot review for that head. +An issue-linked branch without an open PR retains push-time Bugbot. Fork PR +workflows remain outside this contract and MUST stay excluded by the existing +same-repository admission gate. + ### 6.6 State machine | State | Entered when | User-visible meaning | Allowed next states | Recovery/owner | @@ -1018,6 +1033,10 @@ removed. 18. Given the implementation change, then catalog, action/setup schemas, generated bundles, documentation, architecture checks, 128-case budget, and repository validations agree. +19. Given one push creates both `push` and `pull_request:synchronize` runs, then + neither workflow cancels the other, Commit retains progress without running + Bugbot, and the PR event publishes exactly one review projection for the + head. ## 17. Requirements traceability @@ -1028,6 +1047,7 @@ removed. | §6.2 capability behavior | capability outcome adapters/coordinator | route and end-to-end cases | issue/PR/release/Bugbot pages | | §6.4 one canonical card | owned query/mutation ports and reconciler | duplicate/race/pagination tests | operator recovery guide | | §6.5 freshness | source guard and feature revisions | stale/out-of-order/replay tests | observability guide | +| §6.5.1 event ownership | distinct push/PR groups + open-PR Bugbot omission | workflow contract, route unit test, PR #367 paired-event evidence | workflow setup and Bugbot guides | | §7 quiet/image defaults | action/setup configuration policies | action schema, setup, doctor, migration tests | configuration and upgrade pages | | §8 clean boundaries | architecture and source-inventory checks | executable boundary tests | contributor architecture | | §9 message hierarchy/examples | localized feature renderers | semantic golden fixtures and manual UX matrix | user journeys | diff --git a/src/application/usecases/__tests__/commit_use_case.test.ts b/src/application/usecases/__tests__/commit_use_case.test.ts index 869b58ff8..3bd59abd4 100644 --- a/src/application/usecases/__tests__/commit_use_case.test.ts +++ b/src/application/usecases/__tests__/commit_use_case.test.ts @@ -153,6 +153,25 @@ describe('CommitUseCase', () => { expect(results[1].id).toBe('c'); }); + it('keeps push metadata and progress but lets an open pull request own Bugbot review', async () => { + const useCase = new CommitUseCase( + { invoke: mockNotifyInvoke } as any, + { invoke: mockCheckChangesInvoke } as any, + { invoke: mockDetectProblemsInvoke } as any, + { invoke: mockCheckProgressInvoke } as any, + ); + const param = minimalExecution({ + pullRequest: { number: 42, head: 'feature/123', action: '' }, + }); + + await useCase.invoke(param); + + expect(mockNotifyInvoke).toHaveBeenCalledTimes(1); + expect(mockCheckChangesInvoke).toHaveBeenCalledTimes(1); + expect(mockCheckProgressInvoke).toHaveBeenCalledTimes(1); + expect(mockDetectProblemsInvoke).not.toHaveBeenCalled(); + }); + it('on error pushes failure result and rethrows', async () => { mockNotifyInvoke.mockRejectedValue(new Error('step failed')); diff --git a/src/application/usecases/commit_use_case.ts b/src/application/usecases/commit_use_case.ts index 0fe3024a7..f3f77504f 100644 --- a/src/application/usecases/commit_use_case.ts +++ b/src/application/usecases/commit_use_case.ts @@ -52,9 +52,15 @@ export class CommitUseCase implements ParamUseCase { )); if (agentAllowed) { results.push(...(await this.checkProgressUseCase.invoke(projectProgressContext(param)))); - results.push(...(await this.detectPotentialProblemsUseCase.invoke( - projectBugbotReviewOperationContext(param), - ))); + if (param.pullRequest.number > 0) { + logInfo( + `Skipping push Bugbot analysis because pull request #${param.pullRequest.number} owns review for this head.`, + ); + } else { + results.push(...(await this.detectPotentialProblemsUseCase.invoke( + projectBugbotReviewOperationContext(param), + ))); + } } else { logInfo('Skipping push agent analysis because ai-members-only is enabled and the actor is not authorized.'); } diff --git a/src/tooling/__tests__/validate_workflow_contract.test.ts b/src/tooling/__tests__/validate_workflow_contract.test.ts index bf7a2af62..91dce7fd0 100644 --- a/src/tooling/__tests__/validate_workflow_contract.test.ts +++ b/src/tooling/__tests__/validate_workflow_contract.test.ts @@ -117,7 +117,7 @@ describe('workflow contract validator', () => { expect(() => assertQueueBudget(90, 90)).not.toThrow(); }); - it('serializes durable mutations while canceling superseded branch review runs', () => { + it('serializes each branch event owner without cross-canceling the other workflow', () => { for (const directory of ['.github/workflows', 'setup/workflows']) { for (const manifest of QUEUE_WORKFLOW_MANIFEST) { const file = path.join(process.cwd(), directory, manifest.file); @@ -127,7 +127,9 @@ describe('workflow contract validator', () => { expect(workflow.concurrency).toBeUndefined(); if (['copilot_commit.yml', 'copilot_pull_request.yml'].includes(manifest.file)) { expect(workflow.jobs[manifest.jobId].concurrency).toEqual({ - group: 'copilot-bugbot-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}', + group: manifest.file === 'copilot_pull_request.yml' + ? 'copilot-pr-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}' + : 'copilot-push-${{ github.repository }}-${{ github.ref_name }}', 'cancel-in-progress': manifest.file === 'copilot_pull_request.yml' ? "${{ github.event_name != 'pull_request' || github.event.action != 'edited' }}" : true, @@ -153,6 +155,20 @@ describe('workflow contract validator', () => { }, ); + it.each([ + ['copilot_commit.yml', 'copilot-commits', 'copilot-pr-${{ github.repository }}-${{ github.ref_name }}'], + ['copilot_pull_request.yml', 'copilot-pull-requests', 'copilot-push-${{ github.repository }}-${{ github.ref_name }}'], + ])( + 'rejects a cross-workflow concurrency group in %s', + (workflowFile, jobId, sharedGroup) => { + const file = path.join(process.cwd(), '.github/workflows', workflowFile); + const workflow = yaml.load(readFileSync(file, 'utf8')) as MutationWorkflow; + workflow.jobs[jobId].concurrency.group = sharedGroup; + + expect(() => validateWorkflow(file, workflow)).toThrow('avoid cross-canceling the other event owner'); + }, + ); + it('rejects an event workflow that removes the generic bot actor gate', () => { const workflow = JSON.parse(JSON.stringify(validWorkflow)); delete workflow.jobs['copilot-issues'].if; From f1f7d3f96ea9a4ae1e8d35fc239289ba2d5f01bf Mon Sep 17 00:00:00 2001 From: Efra Espada Date: Mon, 14 Sep 2026 15:25:30 +0200 Subject: [PATCH 4/6] codex-semantic-github-publication: preserve legacy reply correlation --- build/github_action/index.js | 17 +++++------ ...tic-github-publication-and-notification.md | 4 +++ .../__tests__/publish_resume_use_case.test.ts | 28 ++++++++++++++++--- .../steps/common/publish_resume_workflow.ts | 18 +++++------- 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/build/github_action/index.js b/build/github_action/index.js index edfcb5653..15a7753ae 100644 --- a/build/github_action/index.js +++ b/build/github_action/index.js @@ -56716,10 +56716,13 @@ function publicationTarget(source) { return positiveInteger(issueNumber) ? Object.freeze({ kind: 'issue', number: issueNumber }) : undefined; } function requestCorrelationId(source, target) { - const commentId = source.inputs?.pull_request_review_comment?.id ?? source.inputs?.comment?.id; - if (positiveInteger(commentId)) { - const transport = safeCorrelationTransport(source.eventName); - return `comment:${transport}:${commentId}`; + const reviewCommentId = source.inputs?.pull_request_review_comment?.id; + if (positiveInteger(reviewCommentId)) { + return `comment:pull_request_review_comment:${reviewCommentId}`; + } + const issueCommentId = source.inputs?.comment?.id; + if (positiveInteger(issueCommentId)) { + return `comment:${issueCommentId}`; } return `event:${(0, publication_identity_policy_1.createSemanticDigest)({ eventName: source.eventName ?? 'unknown', @@ -56727,12 +56730,6 @@ function requestCorrelationId(source, target) { target: target ?? null, })}`; } -function safeCorrelationTransport(eventName) { - const value = eventName?.trim() || 'unknown'; - return /^[A-Za-z0-9._-]{1,64}$/u.test(value) - ? value - : `event-${(0, publication_identity_policy_1.createSemanticDigest)(value)}`; -} function positiveInteger(value) { return typeof value === 'number' && Number.isSafeInteger(value) && value > 0; } diff --git a/specs/semantic-github-publication-and-notification.md b/specs/semantic-github-publication-and-notification.md index 52896b10a..6ea887f7a 100644 --- a/specs/semantic-github-publication-and-notification.md +++ b/specs/semantic-github-publication-and-notification.md @@ -373,6 +373,10 @@ The shared marker format is: `[A-Za-z0-9._:-]`, and bounded to 128 characters per value. - Feature-specific existing markers remain readable. New writes converge on the shared envelope without changing durable feature identity. +- Reply correlation preserves the established `comment:` + identity for `issue_comment` replay compatibility. Review comments use the + distinct `comment:pull_request_review_comment:` identity; + transports MUST NOT share a fallback correlation. - Commit-derived cards MUST revalidate the expected head SHA before update. - Revisioned operations MUST reject any revision lower than the stored revision. - Events without an orderable revision may update only after the shared workflow diff --git a/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts b/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts index 54944a73f..3b47bc923 100644 --- a/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts +++ b/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts @@ -93,11 +93,31 @@ describe('PublishResultUseCase semantic compatibility boundary', () => { await new PublishResultUseCase(comments).invoke(value); expect(comments.addComment).toHaveBeenCalledTimes(1); - expect(comments.values[0].body).toContain('correlation="comment:issue_comment:99"'); + expect(comments.values[0].body).toContain('correlation="comment:99"'); expect(comments.values[0].body).toContain('## Copilot commands'); expect(comments.values[0].body).not.toContain('legacy wrapper'); }); + it('recognizes a reply marker written before review-comment correlation was namespaced', async () => { + const comments = inMemoryComments([{ + id: 7, + user: { login: 'vypbot' }, + body: '\n\nExisting response.', + }]); + const result = new Result({ + id: 'Comment.Help', success: true, executed: true, + payload: { publication: { kind: 'help', botLogin: 'vypbot' } }, + }); + + await new PublishResultUseCase(comments).invoke(projectPublishResultContext(source([result], { + eventName: 'issue_comment', inputs: { action: 'created', comment: { id: 99 } }, + }))); + + expect(comments.addComment).not.toHaveBeenCalled(); + expect(comments.updateComment).not.toHaveBeenCalled(); + expect(comments.values).toHaveLength(1); + }); + it('namespaces equal numeric comment ids by GitHub transport', () => { const issueComment = projectPublishResultContext(source([], { eventName: 'issue_comment', inputs: { action: 'created', comment: { id: 99 } }, @@ -106,7 +126,7 @@ describe('PublishResultUseCase semantic compatibility boundary', () => { eventName: 'pull_request_review_comment', inputs: { action: 'created', pull_request_review_comment: { id: 99 } }, })); - expect(issueComment.requestCorrelationId).toBe('comment:issue_comment:99'); + expect(issueComment.requestCorrelationId).toBe('comment:99'); expect(reviewComment.requestCorrelationId).toBe('comment:pull_request_review_comment:99'); expect(issueComment.requestCorrelationId).not.toBe(reviewComment.requestCorrelationId); }); @@ -126,13 +146,13 @@ describe('PublishResultUseCase semantic compatibility boundary', () => { expect(first.requestCorrelationId).not.toBe(second.requestCorrelationId); }); - it('bounds an unexpected transport value before embedding it in a marker', () => { + it('keeps legacy issue-comment correlation stable regardless of event metadata', () => { const context = projectPublishResultContext(source([], { eventName: 'unsafe transport\n', inputs: { action: 'created', comment: { id: 99 } }, })); - expect(context.requestCorrelationId).toMatch(/^comment:event-[a-f0-9]{16}:99$/u); + expect(context.requestCorrelationId).toBe('comment:99'); }); it('does not mutate an unchanged plan card on replay', async () => { diff --git a/src/application/usecases/steps/common/publish_resume_workflow.ts b/src/application/usecases/steps/common/publish_resume_workflow.ts index 7315abfc4..b6124b139 100644 --- a/src/application/usecases/steps/common/publish_resume_workflow.ts +++ b/src/application/usecases/steps/common/publish_resume_workflow.ts @@ -115,10 +115,13 @@ function publicationTarget(source: PublishResultContextSource): PublicationTarge } function requestCorrelationId(source: PublishResultContextSource, target: PublicationTarget | undefined): string { - const commentId = source.inputs?.pull_request_review_comment?.id ?? source.inputs?.comment?.id; - if (positiveInteger(commentId)) { - const transport = safeCorrelationTransport(source.eventName); - return `comment:${transport}:${commentId}`; + const reviewCommentId = source.inputs?.pull_request_review_comment?.id; + if (positiveInteger(reviewCommentId)) { + return `comment:pull_request_review_comment:${reviewCommentId}`; + } + const issueCommentId = source.inputs?.comment?.id; + if (positiveInteger(issueCommentId)) { + return `comment:${issueCommentId}`; } return `event:${createSemanticDigest({ eventName: source.eventName ?? 'unknown', @@ -127,13 +130,6 @@ function requestCorrelationId(source: PublishResultContextSource, target: Public })}`; } -function safeCorrelationTransport(eventName: string | undefined): string { - const value = eventName?.trim() || 'unknown'; - return /^[A-Za-z0-9._-]{1,64}$/u.test(value) - ? value - : `event-${createSemanticDigest(value)}`; -} - function positiveInteger(value: unknown): value is number { return typeof value === 'number' && Number.isSafeInteger(value) && value > 0; } From 0b114d2ea95620422780b7281bd0c02785bb249f Mon Sep 17 00:00:00 2001 From: Efra Espada Date: Mon, 14 Sep 2026 15:38:19 +0200 Subject: [PATCH 5/6] codex-semantic-github-publication: resolve push review ownership --- build/api/index.js | 79 ++++++++++++++--- build/cli/index.js | 87 +++++++++++++++---- build/github_action/index.js | 87 +++++++++++++++---- docs/bugbot/configuration.mdx | 2 +- docs/bugbot/how-it-works.mdx | 2 +- docs/development/testing.mdx | 5 +- docs/features.mdx | 2 +- specs/CATALOG.md | 8 +- specs/bugbot-review-state-reconciliation.md | 19 ++-- specs/catalog.json | 7 +- ...ssue-and-pull-request-context-hardening.md | 14 +-- ...tic-github-publication-and-notification.md | 17 ++-- .../bugbot_event_ownership_policy.test.ts | 41 +++++++++ .../policies/bugbot_event_ownership_policy.ts | 22 +++++ .../__tests__/commit_use_case.test.ts | 19 ---- src/application/usecases/commit_use_case.ts | 12 +-- ...detect_potential_problems_use_case.test.ts | 30 +++++++ .../load_bugbot_context_use_case.test.ts | 9 +- .../commit/bugbot/bugbot_review_telemetry.ts | 27 ++++-- .../bugbot/load_bugbot_context_use_case.ts | 38 ++++++-- .../detect_potential_problems_workflow.ts | 24 ++++- 21 files changed, 430 insertions(+), 121 deletions(-) create mode 100644 src/application/policies/__tests__/bugbot_event_ownership_policy.test.ts create mode 100644 src/application/policies/bugbot_event_ownership_policy.ts diff --git a/build/api/index.js b/build/api/index.js index a31ef7f3a..6f65de051 100644 --- a/build/api/index.js +++ b/build/api/index.js @@ -148,6 +148,27 @@ exports.BUGBOT_MAX_COMMENTS = 20; exports.BUGBOT_MIN_SEVERITY = 'low'; +/***/ }), + +/***/ 2771: +/***/ ((__unused_webpack_module, exports) => { + + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.selectPullRequestOwnerForPushReview = selectPullRequestOwnerForPushReview; +/** + * Automatic push review yields only after an exact-head provider lookup proves + * that an open same-repository pull request owns the branch revision. + */ +function selectPullRequestOwnerForPushReview(context) { + const pullRequestOwnsReview = context.triggerKind === 'push' + && !context.eventTargetsPullRequest + && context.selectionReason === 'exact-head' + && context.canonicalPullRequest !== null; + return pullRequestOwnsReview ? context.canonicalPullRequest : null; +} + + /***/ }), /***/ 8024: @@ -1844,6 +1865,9 @@ class BugbotReviewTelemetry { this.stages[sanitizeMetricName(stage)] = Math.max(0, this.clock.now() - startedAt); } } + observePreflight(preflight) { + this.preflight = preflight; + } observeContext(context, prompt) { this.context = context; this.promptCharacters = prompt.length; @@ -1860,8 +1884,11 @@ class BugbotReviewTelemetry { } snapshot(outcome, errorCategory) { const changes = this.context?.prContext?.changes ?? []; - const headSha = this.context?.prContext?.prHeadSha; - const canonicalPullRequestNumber = this.context?.canonicalPullRequest?.number; + const canonicalPullRequest = this.context?.canonicalPullRequest + ?? this.preflight?.canonicalPullRequest; + const headSha = this.context?.prContext?.prHeadSha + ?? canonicalPullRequest?.headSha; + const canonicalPullRequestNumber = canonicalPullRequest?.number; const contextCoverage = Object.fromEntries((this.context?.coverage.sources ?? []) .map((source) => [source.source, { status: source.status, @@ -1873,15 +1900,19 @@ class BugbotReviewTelemetry { limitReached: source.limitReached, ...(source.providerLimitReached ? { providerLimitReached: true } : {}), }])); - const providerSources = (this.context?.coverage.sources ?? []).filter((source) => source.pagesFetched > 0 && [ + const observedSources = this.context?.coverage.sources + ?? (this.preflight ? [this.preflight.selectionCoverage] : []); + const providerSources = observedSources.filter((source) => source.pagesFetched > 0 && [ 'selection', 'issue-comments', 'pull-request-comments', 'review-threads', 'diff', ].includes(source.source)); - const selectionCandidates = this.context?.coverage.sources + const selectionCandidates = observedSources .find((source) => source.source === 'selection')?.itemsFetched; + const contextSelectionReason = this.context?.selectionReason + ?? this.preflight?.selectionReason; const repositoryId = this.execution.repository.id; const startedAtEpoch = Date.parse(this.startedAt); const reviewId = [ @@ -1926,11 +1957,13 @@ class BugbotReviewTelemetry { changedFiles: changes.length, changedLines: changes.reduce((sum, change) => sum + change.additions + change.deletions, 0), rulesLoaded: this.context?.reviewRuleSources?.length ?? 0, - ...(this.context ? { - contextSelectionReason: this.context.selectionReason, + ...(contextSelectionReason ? { + contextSelectionReason, ...(selectionCandidates !== undefined ? { contextCandidateBucket: selectionCandidates >= 2 ? '2+' : String(selectionCandidates), } : {}), + } : {}), + ...(this.context ? { contextCoverageStatus: this.context.coverage.status, contextCoverage, } : {}), @@ -2278,6 +2311,7 @@ function applyCommentLimit(findings, maxComments = bugbot_constants_1.BUGBOT_MAX Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.preflightBugbotContext = preflightBugbotContext; exports.loadBugbotContext = loadBugbotContext; const application_error_1 = __nccwpck_require__(5999); const bounded_concurrency_policy_1 = __nccwpck_require__(5596); @@ -2288,10 +2322,19 @@ const bugbot_previous_findings_context_1 = __nccwpck_require__(3346); const bugbot_review_context_1 = __nccwpck_require__(536); const file_ignore_1 = __nccwpck_require__(304); const bugbot_review_rules_1 = __nccwpck_require__(5011); -async function loadBugbotContext(request, ports) { +/** Resolves and validates the provider-owned PR identity without loading review context. */ +async function preflightBugbotContext(request, ports) { const selection = await selectCanonicalPullRequest(request, ports); const canonicalPullRequest = requireUsableSelection(request, selection); - const selectionCoverage = (0, context_1.completeBugbotSourceCoverage)("selection", selection.kind === "canonical" ? 1 : selection.kind === "ambiguous" ? 2 : 0, selectionPageCount(request)); + return { + canonicalPullRequest, + selectionReason: selection.kind === 'canonical' ? selection.reason : 'none', + selectionCoverage: (0, context_1.completeBugbotSourceCoverage)('selection', selection.kind === 'canonical' ? 1 : selection.kind === 'ambiguous' ? 2 : 0, selectionPageCount(request)), + }; +} +async function loadBugbotContext(request, ports, resolvedPreflight) { + const preflight = resolvedPreflight ?? await preflightBugbotContext(request, ports); + const { canonicalPullRequest, selectionCoverage, selectionReason } = preflight; const tasks = []; if (request.target.issueNumber !== undefined) { tasks.push(async () => { @@ -2367,12 +2410,12 @@ async function loadBugbotContext(request, ports) { limitReached: ruleSet.omitted > 0, }, ]); - (0, logging_ports_1.logDebugInfo)(`LoadBugbotContext: selection=${selection.kind}, coverage=${coverage.status}, existing findings=${Object.keys(parsedComments.existingByFindingId).length}, retained previous findings=${previousContext.selected.length}, diff files=${prContext?.changes?.length ?? 0}.`); + (0, logging_ports_1.logDebugInfo)(`LoadBugbotContext: selection=${selectionReason}, coverage=${coverage.status}, existing findings=${Object.keys(parsedComments.existingByFindingId).length}, retained previous findings=${previousContext.selected.length}, diff files=${prContext?.changes?.length ?? 0}.`); return { existingByFindingId: parsedComments.existingByFindingId, issueComments: parsedComments.issueComments, canonicalPullRequest, - selectionReason: selection.kind === "canonical" ? selection.reason : "none", + selectionReason, coverage, eligibleResolutionIds: new Set(previousContext.selected.map((finding) => finding.id)), previousFindingsBlock: previousContext.block, @@ -3684,6 +3727,7 @@ const analyze_bugbot_revision_use_case_1 = __nccwpck_require__(4658); const bugbot_review_freshness_1 = __nccwpck_require__(4307); const reconcile_bugbot_review_state_use_case_1 = __nccwpck_require__(7515); const application_error_1 = __nccwpck_require__(5999); +const bugbot_event_ownership_policy_1 = __nccwpck_require__(2771); const TASK_ID = 'DetectPotentialProblemsUseCase'; /** Coordinates Bugbot context, analysis and finding publication behind application ports. */ async function runDetectPotentialProblemsWorkflow(reviewContext, dependencies) { @@ -3726,7 +3770,20 @@ async function runDetectPotentialProblemsWorkflow(reviewContext, dependencies) { return []; } const contextRequest = (0, bugbot_context_request_1.projectBugbotContextRequest)(reviewContext, contextOptions); - const context = await telemetry.measure('context', () => (0, load_bugbot_context_use_case_1.loadBugbotContext)(contextRequest, dependencies.scm.context)); + const preflight = await telemetry.measure('context-preflight', () => (0, load_bugbot_context_use_case_1.preflightBugbotContext)(contextRequest, dependencies.scm.context)); + telemetry.observePreflight(preflight); + const owningPullRequest = (0, bugbot_event_ownership_policy_1.selectPullRequestOwnerForPushReview)({ + triggerKind: reviewContext.trigger.kind, + eventTargetsPullRequest: reviewContext.target.isPullRequest, + selectionReason: preflight.selectionReason, + canonicalPullRequest: preflight.canonicalPullRequest, + }); + if (owningPullRequest) { + (0, logging_ports_1.logInfo)(`Skipping push Bugbot analysis because pull request #${owningPullRequest.number} owns review for this head.`); + await publishTelemetry('skipped', 'pull_request_ownership'); + return []; + } + const context = await telemetry.measure('context', () => (0, load_bugbot_context_use_case_1.loadBugbotContext)(contextRequest, dependencies.scm.context, preflight)); const eventHeadSha = reviewContext.trigger.expectedHeadSha; if ((0, bugbot_review_freshness_1.isLoadedBugbotRevisionSuperseded)(context, eventHeadSha)) { return await complete(supersededResult(context.prContext?.prHeadSha, eventHeadSha), 'superseded'); diff --git a/build/cli/index.js b/build/cli/index.js index 3009b4d9e..3cb518a73 100755 --- a/build/cli/index.js +++ b/build/cli/index.js @@ -41617,6 +41617,28 @@ exports.BUGBOT_MAX_COMMENTS = 20; exports.BUGBOT_MIN_SEVERITY = 'low'; +/***/ }), + +/***/ 52771: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.selectPullRequestOwnerForPushReview = selectPullRequestOwnerForPushReview; +/** + * Automatic push review yields only after an exact-head provider lookup proves + * that an open same-repository pull request owns the branch revision. + */ +function selectPullRequestOwnerForPushReview(context) { + const pullRequestOwnsReview = context.triggerKind === 'push' + && !context.eventTargetsPullRequest + && context.selectionReason === 'exact-head' + && context.canonicalPullRequest !== null; + return pullRequestOwnsReview ? context.canonicalPullRequest : null; +} + + /***/ }), /***/ 98024: @@ -49080,12 +49102,7 @@ class CommitUseCase { || Boolean(this.actorAuthorizationPort && await this.actorAuthorizationPort.isActorAllowedToModifyFiles(param.owner, param.repo, param.actor, param.tokens.token)); if (agentAllowed) { results.push(...(await this.checkProgressUseCase.invoke((0, push_single_action_contexts_1.projectProgressContext)(param)))); - if (param.pullRequest.number > 0) { - (0, logging_ports_1.logInfo)(`Skipping push Bugbot analysis because pull request #${param.pullRequest.number} owns review for this head.`); - } - else { - results.push(...(await this.detectPotentialProblemsUseCase.invoke((0, bugbot_review_operation_context_1.projectBugbotReviewOperationContext)(param)))); - } + results.push(...(await this.detectPotentialProblemsUseCase.invoke((0, bugbot_review_operation_context_1.projectBugbotReviewOperationContext)(param)))); } else { (0, logging_ports_1.logInfo)('Skipping push agent analysis because ai-members-only is enabled and the actor is not authorized.'); @@ -52391,6 +52408,9 @@ class BugbotReviewTelemetry { this.stages[sanitizeMetricName(stage)] = Math.max(0, this.clock.now() - startedAt); } } + observePreflight(preflight) { + this.preflight = preflight; + } observeContext(context, prompt) { this.context = context; this.promptCharacters = prompt.length; @@ -52407,8 +52427,11 @@ class BugbotReviewTelemetry { } snapshot(outcome, errorCategory) { const changes = this.context?.prContext?.changes ?? []; - const headSha = this.context?.prContext?.prHeadSha; - const canonicalPullRequestNumber = this.context?.canonicalPullRequest?.number; + const canonicalPullRequest = this.context?.canonicalPullRequest + ?? this.preflight?.canonicalPullRequest; + const headSha = this.context?.prContext?.prHeadSha + ?? canonicalPullRequest?.headSha; + const canonicalPullRequestNumber = canonicalPullRequest?.number; const contextCoverage = Object.fromEntries((this.context?.coverage.sources ?? []) .map((source) => [source.source, { status: source.status, @@ -52420,15 +52443,19 @@ class BugbotReviewTelemetry { limitReached: source.limitReached, ...(source.providerLimitReached ? { providerLimitReached: true } : {}), }])); - const providerSources = (this.context?.coverage.sources ?? []).filter((source) => source.pagesFetched > 0 && [ + const observedSources = this.context?.coverage.sources + ?? (this.preflight ? [this.preflight.selectionCoverage] : []); + const providerSources = observedSources.filter((source) => source.pagesFetched > 0 && [ 'selection', 'issue-comments', 'pull-request-comments', 'review-threads', 'diff', ].includes(source.source)); - const selectionCandidates = this.context?.coverage.sources + const selectionCandidates = observedSources .find((source) => source.source === 'selection')?.itemsFetched; + const contextSelectionReason = this.context?.selectionReason + ?? this.preflight?.selectionReason; const repositoryId = this.execution.repository.id; const startedAtEpoch = Date.parse(this.startedAt); const reviewId = [ @@ -52473,11 +52500,13 @@ class BugbotReviewTelemetry { changedFiles: changes.length, changedLines: changes.reduce((sum, change) => sum + change.additions + change.deletions, 0), rulesLoaded: this.context?.reviewRuleSources?.length ?? 0, - ...(this.context ? { - contextSelectionReason: this.context.selectionReason, + ...(contextSelectionReason ? { + contextSelectionReason, ...(selectionCandidates !== undefined ? { contextCandidateBucket: selectionCandidates >= 2 ? '2+' : String(selectionCandidates), } : {}), + } : {}), + ...(this.context ? { contextCoverageStatus: this.context.coverage.status, contextCoverage, } : {}), @@ -53446,6 +53475,7 @@ function applyCommentLimit(findings, maxComments = bugbot_constants_1.BUGBOT_MAX "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.preflightBugbotContext = preflightBugbotContext; exports.loadBugbotContext = loadBugbotContext; const application_error_1 = __nccwpck_require__(75999); const bounded_concurrency_policy_1 = __nccwpck_require__(35596); @@ -53456,10 +53486,19 @@ const bugbot_previous_findings_context_1 = __nccwpck_require__(3346); const bugbot_review_context_1 = __nccwpck_require__(50536); const file_ignore_1 = __nccwpck_require__(10304); const bugbot_review_rules_1 = __nccwpck_require__(25011); -async function loadBugbotContext(request, ports) { +/** Resolves and validates the provider-owned PR identity without loading review context. */ +async function preflightBugbotContext(request, ports) { const selection = await selectCanonicalPullRequest(request, ports); const canonicalPullRequest = requireUsableSelection(request, selection); - const selectionCoverage = (0, context_1.completeBugbotSourceCoverage)("selection", selection.kind === "canonical" ? 1 : selection.kind === "ambiguous" ? 2 : 0, selectionPageCount(request)); + return { + canonicalPullRequest, + selectionReason: selection.kind === 'canonical' ? selection.reason : 'none', + selectionCoverage: (0, context_1.completeBugbotSourceCoverage)('selection', selection.kind === 'canonical' ? 1 : selection.kind === 'ambiguous' ? 2 : 0, selectionPageCount(request)), + }; +} +async function loadBugbotContext(request, ports, resolvedPreflight) { + const preflight = resolvedPreflight ?? await preflightBugbotContext(request, ports); + const { canonicalPullRequest, selectionCoverage, selectionReason } = preflight; const tasks = []; if (request.target.issueNumber !== undefined) { tasks.push(async () => { @@ -53535,12 +53574,12 @@ async function loadBugbotContext(request, ports) { limitReached: ruleSet.omitted > 0, }, ]); - (0, logging_ports_1.logDebugInfo)(`LoadBugbotContext: selection=${selection.kind}, coverage=${coverage.status}, existing findings=${Object.keys(parsedComments.existingByFindingId).length}, retained previous findings=${previousContext.selected.length}, diff files=${prContext?.changes?.length ?? 0}.`); + (0, logging_ports_1.logDebugInfo)(`LoadBugbotContext: selection=${selectionReason}, coverage=${coverage.status}, existing findings=${Object.keys(parsedComments.existingByFindingId).length}, retained previous findings=${previousContext.selected.length}, diff files=${prContext?.changes?.length ?? 0}.`); return { existingByFindingId: parsedComments.existingByFindingId, issueComments: parsedComments.issueComments, canonicalPullRequest, - selectionReason: selection.kind === "canonical" ? selection.reason : "none", + selectionReason, coverage, eligibleResolutionIds: new Set(previousContext.selected.map((finding) => finding.id)), previousFindingsBlock: previousContext.block, @@ -55223,6 +55262,7 @@ const analyze_bugbot_revision_use_case_1 = __nccwpck_require__(4658); const bugbot_review_freshness_1 = __nccwpck_require__(14307); const reconcile_bugbot_review_state_use_case_1 = __nccwpck_require__(57515); const application_error_1 = __nccwpck_require__(75999); +const bugbot_event_ownership_policy_1 = __nccwpck_require__(52771); const TASK_ID = 'DetectPotentialProblemsUseCase'; /** Coordinates Bugbot context, analysis and finding publication behind application ports. */ async function runDetectPotentialProblemsWorkflow(reviewContext, dependencies) { @@ -55265,7 +55305,20 @@ async function runDetectPotentialProblemsWorkflow(reviewContext, dependencies) { return []; } const contextRequest = (0, bugbot_context_request_1.projectBugbotContextRequest)(reviewContext, contextOptions); - const context = await telemetry.measure('context', () => (0, load_bugbot_context_use_case_1.loadBugbotContext)(contextRequest, dependencies.scm.context)); + const preflight = await telemetry.measure('context-preflight', () => (0, load_bugbot_context_use_case_1.preflightBugbotContext)(contextRequest, dependencies.scm.context)); + telemetry.observePreflight(preflight); + const owningPullRequest = (0, bugbot_event_ownership_policy_1.selectPullRequestOwnerForPushReview)({ + triggerKind: reviewContext.trigger.kind, + eventTargetsPullRequest: reviewContext.target.isPullRequest, + selectionReason: preflight.selectionReason, + canonicalPullRequest: preflight.canonicalPullRequest, + }); + if (owningPullRequest) { + (0, logging_ports_1.logInfo)(`Skipping push Bugbot analysis because pull request #${owningPullRequest.number} owns review for this head.`); + await publishTelemetry('skipped', 'pull_request_ownership'); + return []; + } + const context = await telemetry.measure('context', () => (0, load_bugbot_context_use_case_1.loadBugbotContext)(contextRequest, dependencies.scm.context, preflight)); const eventHeadSha = reviewContext.trigger.expectedHeadSha; if ((0, bugbot_review_freshness_1.isLoadedBugbotRevisionSuperseded)(context, eventHeadSha)) { return await complete(supersededResult(context.prContext?.prHeadSha, eventHeadSha), 'superseded'); diff --git a/build/github_action/index.js b/build/github_action/index.js index 15a7753ae..1553aded4 100644 --- a/build/github_action/index.js +++ b/build/github_action/index.js @@ -42267,6 +42267,28 @@ exports.BUGBOT_MAX_COMMENTS = 20; exports.BUGBOT_MIN_SEVERITY = 'low'; +/***/ }), + +/***/ 52771: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.selectPullRequestOwnerForPushReview = selectPullRequestOwnerForPushReview; +/** + * Automatic push review yields only after an exact-head provider lookup proves + * that an open same-repository pull request owns the branch revision. + */ +function selectPullRequestOwnerForPushReview(context) { + const pullRequestOwnsReview = context.triggerKind === 'push' + && !context.eventTargetsPullRequest + && context.selectionReason === 'exact-head' + && context.canonicalPullRequest !== null; + return pullRequestOwnsReview ? context.canonicalPullRequest : null; +} + + /***/ }), /***/ 98024: @@ -49898,12 +49920,7 @@ class CommitUseCase { || Boolean(this.actorAuthorizationPort && await this.actorAuthorizationPort.isActorAllowedToModifyFiles(param.owner, param.repo, param.actor, param.tokens.token)); if (agentAllowed) { results.push(...(await this.checkProgressUseCase.invoke((0, push_single_action_contexts_1.projectProgressContext)(param)))); - if (param.pullRequest.number > 0) { - (0, logging_ports_1.logInfo)(`Skipping push Bugbot analysis because pull request #${param.pullRequest.number} owns review for this head.`); - } - else { - results.push(...(await this.detectPotentialProblemsUseCase.invoke((0, bugbot_review_operation_context_1.projectBugbotReviewOperationContext)(param)))); - } + results.push(...(await this.detectPotentialProblemsUseCase.invoke((0, bugbot_review_operation_context_1.projectBugbotReviewOperationContext)(param)))); } else { (0, logging_ports_1.logInfo)('Skipping push agent analysis because ai-members-only is enabled and the actor is not authorized.'); @@ -52462,6 +52479,9 @@ class BugbotReviewTelemetry { this.stages[sanitizeMetricName(stage)] = Math.max(0, this.clock.now() - startedAt); } } + observePreflight(preflight) { + this.preflight = preflight; + } observeContext(context, prompt) { this.context = context; this.promptCharacters = prompt.length; @@ -52478,8 +52498,11 @@ class BugbotReviewTelemetry { } snapshot(outcome, errorCategory) { const changes = this.context?.prContext?.changes ?? []; - const headSha = this.context?.prContext?.prHeadSha; - const canonicalPullRequestNumber = this.context?.canonicalPullRequest?.number; + const canonicalPullRequest = this.context?.canonicalPullRequest + ?? this.preflight?.canonicalPullRequest; + const headSha = this.context?.prContext?.prHeadSha + ?? canonicalPullRequest?.headSha; + const canonicalPullRequestNumber = canonicalPullRequest?.number; const contextCoverage = Object.fromEntries((this.context?.coverage.sources ?? []) .map((source) => [source.source, { status: source.status, @@ -52491,15 +52514,19 @@ class BugbotReviewTelemetry { limitReached: source.limitReached, ...(source.providerLimitReached ? { providerLimitReached: true } : {}), }])); - const providerSources = (this.context?.coverage.sources ?? []).filter((source) => source.pagesFetched > 0 && [ + const observedSources = this.context?.coverage.sources + ?? (this.preflight ? [this.preflight.selectionCoverage] : []); + const providerSources = observedSources.filter((source) => source.pagesFetched > 0 && [ 'selection', 'issue-comments', 'pull-request-comments', 'review-threads', 'diff', ].includes(source.source)); - const selectionCandidates = this.context?.coverage.sources + const selectionCandidates = observedSources .find((source) => source.source === 'selection')?.itemsFetched; + const contextSelectionReason = this.context?.selectionReason + ?? this.preflight?.selectionReason; const repositoryId = this.execution.repository.id; const startedAtEpoch = Date.parse(this.startedAt); const reviewId = [ @@ -52544,11 +52571,13 @@ class BugbotReviewTelemetry { changedFiles: changes.length, changedLines: changes.reduce((sum, change) => sum + change.additions + change.deletions, 0), rulesLoaded: this.context?.reviewRuleSources?.length ?? 0, - ...(this.context ? { - contextSelectionReason: this.context.selectionReason, + ...(contextSelectionReason ? { + contextSelectionReason, ...(selectionCandidates !== undefined ? { contextCandidateBucket: selectionCandidates >= 2 ? '2+' : String(selectionCandidates), } : {}), + } : {}), + ...(this.context ? { contextCoverageStatus: this.context.coverage.status, contextCoverage, } : {}), @@ -53517,6 +53546,7 @@ function applyCommentLimit(findings, maxComments = bugbot_constants_1.BUGBOT_MAX "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.preflightBugbotContext = preflightBugbotContext; exports.loadBugbotContext = loadBugbotContext; const application_error_1 = __nccwpck_require__(75999); const bounded_concurrency_policy_1 = __nccwpck_require__(35596); @@ -53527,10 +53557,19 @@ const bugbot_previous_findings_context_1 = __nccwpck_require__(3346); const bugbot_review_context_1 = __nccwpck_require__(50536); const file_ignore_1 = __nccwpck_require__(10304); const bugbot_review_rules_1 = __nccwpck_require__(25011); -async function loadBugbotContext(request, ports) { +/** Resolves and validates the provider-owned PR identity without loading review context. */ +async function preflightBugbotContext(request, ports) { const selection = await selectCanonicalPullRequest(request, ports); const canonicalPullRequest = requireUsableSelection(request, selection); - const selectionCoverage = (0, context_1.completeBugbotSourceCoverage)("selection", selection.kind === "canonical" ? 1 : selection.kind === "ambiguous" ? 2 : 0, selectionPageCount(request)); + return { + canonicalPullRequest, + selectionReason: selection.kind === 'canonical' ? selection.reason : 'none', + selectionCoverage: (0, context_1.completeBugbotSourceCoverage)('selection', selection.kind === 'canonical' ? 1 : selection.kind === 'ambiguous' ? 2 : 0, selectionPageCount(request)), + }; +} +async function loadBugbotContext(request, ports, resolvedPreflight) { + const preflight = resolvedPreflight ?? await preflightBugbotContext(request, ports); + const { canonicalPullRequest, selectionCoverage, selectionReason } = preflight; const tasks = []; if (request.target.issueNumber !== undefined) { tasks.push(async () => { @@ -53606,12 +53645,12 @@ async function loadBugbotContext(request, ports) { limitReached: ruleSet.omitted > 0, }, ]); - (0, logging_ports_1.logDebugInfo)(`LoadBugbotContext: selection=${selection.kind}, coverage=${coverage.status}, existing findings=${Object.keys(parsedComments.existingByFindingId).length}, retained previous findings=${previousContext.selected.length}, diff files=${prContext?.changes?.length ?? 0}.`); + (0, logging_ports_1.logDebugInfo)(`LoadBugbotContext: selection=${selectionReason}, coverage=${coverage.status}, existing findings=${Object.keys(parsedComments.existingByFindingId).length}, retained previous findings=${previousContext.selected.length}, diff files=${prContext?.changes?.length ?? 0}.`); return { existingByFindingId: parsedComments.existingByFindingId, issueComments: parsedComments.issueComments, canonicalPullRequest, - selectionReason: selection.kind === "canonical" ? selection.reason : "none", + selectionReason, coverage, eligibleResolutionIds: new Set(previousContext.selected.map((finding) => finding.id)), previousFindingsBlock: previousContext.block, @@ -55294,6 +55333,7 @@ const analyze_bugbot_revision_use_case_1 = __nccwpck_require__(4658); const bugbot_review_freshness_1 = __nccwpck_require__(14307); const reconcile_bugbot_review_state_use_case_1 = __nccwpck_require__(57515); const application_error_1 = __nccwpck_require__(75999); +const bugbot_event_ownership_policy_1 = __nccwpck_require__(52771); const TASK_ID = 'DetectPotentialProblemsUseCase'; /** Coordinates Bugbot context, analysis and finding publication behind application ports. */ async function runDetectPotentialProblemsWorkflow(reviewContext, dependencies) { @@ -55336,7 +55376,20 @@ async function runDetectPotentialProblemsWorkflow(reviewContext, dependencies) { return []; } const contextRequest = (0, bugbot_context_request_1.projectBugbotContextRequest)(reviewContext, contextOptions); - const context = await telemetry.measure('context', () => (0, load_bugbot_context_use_case_1.loadBugbotContext)(contextRequest, dependencies.scm.context)); + const preflight = await telemetry.measure('context-preflight', () => (0, load_bugbot_context_use_case_1.preflightBugbotContext)(contextRequest, dependencies.scm.context)); + telemetry.observePreflight(preflight); + const owningPullRequest = (0, bugbot_event_ownership_policy_1.selectPullRequestOwnerForPushReview)({ + triggerKind: reviewContext.trigger.kind, + eventTargetsPullRequest: reviewContext.target.isPullRequest, + selectionReason: preflight.selectionReason, + canonicalPullRequest: preflight.canonicalPullRequest, + }); + if (owningPullRequest) { + (0, logging_ports_1.logInfo)(`Skipping push Bugbot analysis because pull request #${owningPullRequest.number} owns review for this head.`); + await publishTelemetry('skipped', 'pull_request_ownership'); + return []; + } + const context = await telemetry.measure('context', () => (0, load_bugbot_context_use_case_1.loadBugbotContext)(contextRequest, dependencies.scm.context, preflight)); const eventHeadSha = reviewContext.trigger.expectedHeadSha; if ((0, bugbot_review_freshness_1.isLoadedBugbotRevisionSuperseded)(context, eventHeadSha)) { return await complete(supersededResult(context.prContext?.prHeadSha, eventHeadSha), 'superseded'); diff --git a/docs/bugbot/configuration.mdx b/docs/bugbot/configuration.mdx index 24dfd77fc..6302e5664 100644 --- a/docs/bugbot/configuration.mdx +++ b/docs/bugbot/configuration.mdx @@ -121,7 +121,7 @@ Use repository-specific review and protected-branch controls in addition to igno Detection is read-oriented, but publication requires the GitHub permissions needed to create issue comments or pull-request review comments. Autofix and user-request workflows additionally require write permission and must be restricted to trusted actors and trusted code. -PR reconciliation also reads submitted reviews and updates their generated status blocks, then upserts one issue-comment status card. Keep `pull-requests: write` and `issues: write` available to the configured PAT. No additional Bugbot toggle or secret is required. The shipped Commit and Pull Request workflows use distinct repository/branch native concurrency keys, so they cannot cancel one another. When Commit discovers an open same-repository PR, it skips Bugbot and lets `pull_request:synchronize` own review for that head; branches without a PR retain push-time review. A metadata-only `pull_request: edited` run waits instead of preempting an active PR review. Copy both templates together so this ownership contract remains intact. +PR reconciliation also reads submitted reviews and updates their generated status blocks, then upserts one issue-comment status card. Keep `pull-requests: write` and `issues: write` available to the configured PAT. No additional Bugbot toggle or secret is required. The shipped Commit and Pull Request workflows use distinct repository/branch native concurrency keys, so they cannot cancel one another. On push, Bugbot uses an exact-head provider lookup to detect an open same-repository PR before loading review context or invoking the agent; a match yields review ownership to `pull_request:synchronize`. Branches without a PR retain push-time review. A metadata-only `pull_request: edited` run waits instead of preempting an active PR review. Copy both templates together so this ownership contract remains intact. Do not expose provider credentials to fork code, untrusted pull requests, or `pull_request_target` workflows that execute attacker-controlled code. Prefer private repositories, protected branches, reviewed workflows, approved environments, and controlled runners. diff --git a/docs/bugbot/how-it-works.mdx b/docs/bugbot/how-it-works.mdx index c0e07861e..17ecb66fa 100644 --- a/docs/bugbot/how-it-works.mdx +++ b/docs/bugbot/how-it-works.mdx @@ -81,7 +81,7 @@ This page describes the **internal flow** of Bugbot: how detection runs, how the 7. **Re-read and project:** After mutations, Bugbot verifies the PR head, re-reads linked-issue findings, reviews, child comments, native thread facts, and the PR conversation concurrently, and then verifies the head again. If the head changed while those reads were in flight, the whole snapshot is discarded as superseded. Each surface records whether its read was verified, failed, or not applicable, and each non-clean issue and PR destination must be observed independently; one visible or clean destination cannot hide another that is open, unverifiable, or missing. Pure lifecycle, provider-projection, and reconciliation-plan policies derive `open`, `reopened`, `fixed`, `obsolete`, `dismissed`, `verification-required`, or `unknown`. A dedicated presentation use case then updates at most 20 affected historical review blocks per run with bounded concurrency, upserts the oldest trusted **Bugbot status** card, and feeds the Result, lifecycle labels, Job Summary, Check Run, and telemetry. Missing or malformed owned evidence fails closed; it is never presented as clean. User-facing PR, commit, run, review, and finding links come only from authenticated provider adapters. Finding links are accepted only when they belong to the same HTTPS server and repository, including GitHub Enterprise installations. -Review and Commit workflow templates use distinct repository-and-branch concurrency keys. Each can cancel only a superseded run from the same event owner; a paired `push` and `pull_request:synchronize` therefore cannot cancel one another. Once Commit discovers an open same-repository PR, it retains issue progress and native push work but skips Bugbot, because the PR synchronization event exclusively owns review for that head. Branches without an open PR still receive push-time, issue-targeted Bugbot review. Fork PR jobs remain excluded by the workflow's same-repository admission gate. A `pull_request: edited` event uses the PR key but cannot cancel a running PR review; it waits, and redundant pending edits collapse to the newest event. When it later runs, it keeps its native workflow result and Job Summary but does not create a `Copilot / Review` Check because it has no Bugbot telemetry. The Check name is reserved for exactly one structurally valid analysis snapshot for the exact head; duplicate telemetry or a valid snapshot beside malformed telemetry is rejected as ambiguous. Application head guards and idempotent provider writes still protect partial/canceled transitions. Other durable mutation workflows retain their workflow-local queue. +Review and Commit workflow templates use distinct repository-and-branch concurrency keys. Each can cancel only a superseded run from the same event owner; a paired `push` and `pull_request:synchronize` therefore cannot cancel one another. Commit retains issue progress and native push work, then Bugbot resolves the branch with a read-only exact-head provider lookup. A validated open same-repository PR makes the push stop before loading review context or invoking the agent, because the PR synchronization event exclusively owns review for that head. This does not depend on PR identity being present in the `push` payload. Branches without an open PR still receive push-time, issue-targeted Bugbot review. Fork PR jobs remain excluded by the workflow's same-repository admission gate. A `pull_request: edited` event uses the PR key but cannot cancel a running PR review; it waits, and redundant pending edits collapse to the newest event. When it later runs, it keeps its native workflow result and Job Summary but does not create a `Copilot / Review` Check because it has no Bugbot telemetry. The Check name is reserved for exactly one structurally valid analysis snapshot for the exact head; duplicate telemetry or a valid snapshot beside malformed telemetry is rejected as ambiguous. Application head guards and idempotent provider writes still protect partial/canceled transitions. Other durable mutation workflows retain their workflow-local queue. Every exit path emits optional content-free telemetry, including canonical selection reason, logical and raw request counts, the fixed concurrency limit, diff --git a/docs/development/testing.mdx b/docs/development/testing.mdx index 41d3cee9a..f761a06c0 100644 --- a/docs/development/testing.mdx +++ b/docs/development/testing.mdx @@ -56,7 +56,10 @@ The workflow contract test keeps the nine queue-bearing workflow names synchroni validator and verifies the `90m queue / 120m job` budget, required runners, action inputs, missing/short timeouts, forbidden durable-mutation concurrency, and the distinct branch concurrency and event-ownership contracts for Commit and Pull -Request jobs. +Request jobs. The Bugbot ownership policy matrix and push-shaped integration +test additionally prove that exact-head provider discovery—not a nonexistent +`pull_request` field on the push payload—stops duplicate agent review before +the diff and conversation are loaded. GitHub lifecycle tests cover admission before queue polling, PAT-owner normal-run discard, valid single-action preservation, identity lookup failure, and the absence diff --git a/docs/features.mdx b/docs/features.mdx index b3fff9961..60e7762ad 100644 --- a/docs/features.mdx +++ b/docs/features.mdx @@ -161,7 +161,7 @@ are deprecated. **Two coordination policies:** durable mutation workflows preserve every admitted event in a workflow-local queue. Commit and Pull Request each have a separate repository/branch latest-revision lane, so paired events cannot cancel one another. -GitHub's native [concurrency](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency) can cancel in-progress runs when a newer one starts and can retain only one pending run. The replaceable branch jobs therefore use event-specific keys: Commit uses `copilot-push-${{ github.repository }}-${{ github.ref_name }}` and Pull Request uses `copilot-pr-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}`. Each cancels only its own obsolete revision. When Commit discovers an open same-repository PR, it still synchronizes issue progress but skips Bugbot; the PR `synchronize` event exclusively owns review for that head. This prevents duplicate reviews without letting one event cancel useful work from the other. Branches without an open PR retain push-time Bugbot. The PR workflow conditionally disables cancellation for `pull_request: edited`, so metadata normalization waits instead of preempting the active PR review. Application-level head guards prevent an older run from updating a newer PR, and the next run repairs any durable half-transition discovered after cancellation. +GitHub's native [concurrency](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency) can cancel in-progress runs when a newer one starts and can retain only one pending run. The replaceable branch jobs therefore use event-specific keys: Commit uses `copilot-push-${{ github.repository }}-${{ github.ref_name }}` and Pull Request uses `copilot-pr-${{ github.repository }}-${{ github.event.pull_request.head.ref || github.ref_name }}`. Each cancels only its own obsolete revision. Commit still synchronizes issue progress, then Bugbot performs a read-only exact-head lookup because a `push` payload does not contain PR identity. A validated open same-repository PR makes the push stop before review-context loading or agent invocation; the PR `synchronize` event exclusively owns review for that head. This prevents duplicate reviews without letting one event cancel useful work from the other. Branches without an open PR retain push-time Bugbot. The PR workflow conditionally disables cancellation for `pull_request: edited`, so metadata normalization waits instead of preempting the active PR review. Application-level head guards prevent an older run from updating a newer PR, and the next run repairs any durable half-transition discovered after cancellation. For non-replaceable issue and comment mutations, Copilot adds an application-level queue per workflow: every started run waits for earlier active runs of that same workflow, so intermediate events are not discarded by a native concurrency group. Runs triggered by the PAT owner that would only re-trigger the normal pipeline complete before entering this queue. diff --git a/specs/CATALOG.md b/specs/CATALOG.md index 21ca4affd..1dc860731 100644 --- a/specs/CATALOG.md +++ b/specs/CATALOG.md @@ -13,7 +13,7 @@ debt or convert unknown historic intent into a design decision. | `github-communication-experience` | Proposed | English-default, localized, semantic, bounded, and idempotent product messages across GitHub and repository-aware operator surfaces | [Semantic GitHub communication and repository localization](./semantic-github-publication-and-notification.md) + 1 companion | 75 paths · 2026-09-14 | | `release-orchestration` | Implemented | Release and hotfix promotion, publication, reconciliation, and durable recovery | [Configurable production-first release orchestration](./configurable-release-orchestration.md) + 2 companion | 41 paths · 2026-09-12 | | `merge-queue-readiness` | Implemented | Fail-closed validation of required checks and merge-group workflow support | [Merge queue readiness and effective target rules](./merge-queue-readiness.md) | 15 paths · 2026-09-11 | -| `bugbot-review-state-reconciliation` | Implemented | Reconcile review snapshots, findings, threads, comments, and check conclusions | [Bugbot review-state reconciliation](./bugbot-review-state-reconciliation.md) | 42 paths · 2026-09-14 | +| `bugbot-review-state-reconciliation` | Implemented | Reconcile review snapshots, findings, threads, comments, and check conclusions | [Bugbot review-state reconciliation](./bugbot-review-state-reconciliation.md) | 45 paths · 2026-09-14 | | `execution-lifecycle` | Implemented | Shared GitHub Action lifecycle from event admission through durable user-facing results | [Execution admission, queueing, routing, and result publication](./execution-admission-queue-and-publication.md) + 3 companion | 82 paths · 2026-09-14 | | `architecture-quality-hardening` | Implemented | Close verified concurrency, error-contract, context-coupling, fan-out, setup/doctor, and provider-policy risks in dependency order | [Architecture quality and scalability hardening](./architecture-quality-and-scalability-hardening.md) + 1 companion | 71 paths · 2026-09-14 | | `setup-and-doctor` | Implemented | Plan, validate, provision, and audit a repository installation without exposing credentials | [Setup, configuration, credentials, and doctor](./setup-configuration-credentials-and-doctor.md) + 1 companion | 39 paths · 2026-09-12 | @@ -66,9 +66,9 @@ debt or convert unknown historic intent into a design decision. - Last verified: 2026-09-14 - Specifications: [`specs/bugbot-review-state-reconciliation.md`](./bugbot-review-state-reconciliation.md) - Workflows: [`.github/workflows/copilot_commit.yml`](../.github/workflows/copilot_commit.yml) · [`.github/workflows/copilot_pull_request.yml`](../.github/workflows/copilot_pull_request.yml) · [`.github/workflows/copilot_pull_request_comment.yml`](../.github/workflows/copilot_pull_request_comment.yml) -- Entrypoints: [`src/application/usecases/commit_use_case.ts`](../src/application/usecases/commit_use_case.ts) · [`src/application/usecases/steps/commit/bugbot/reconcile_bugbot_review_state_use_case.ts`](../src/application/usecases/steps/commit/bugbot/reconcile_bugbot_review_state_use_case.ts) · [`src/actions/github_action_completion.ts`](../src/actions/github_action_completion.ts) -- Core code: [`src/domain/bugbot/review_state.ts`](../src/domain/bugbot/review_state.ts) · [`src/domain/bugbot/review_projection.ts`](../src/domain/bugbot/review_projection.ts) · [`src/application/policies/bugbot_reconciliation_policy.ts`](../src/application/policies/bugbot_reconciliation_policy.ts) · [`src/application/policies/bugbot_result_finding_state_projection_policy.ts`](../src/application/policies/bugbot_result_finding_state_projection_policy.ts) · [`src/application/policies/bugbot_telemetry_projection_policy.ts`](../src/application/policies/bugbot_telemetry_projection_policy.ts) · [`src/application/policies/action_summary_policy.ts`](../src/application/policies/action_summary_policy.ts) · [`src/application/policies/copilot_evidence_policy.ts`](../src/application/policies/copilot_evidence_policy.ts) · [`src/application/policies/lifecycle_state_policy.ts`](../src/application/policies/lifecycle_state_policy.ts) · [`src/application/policies/status_command_policy.ts`](../src/application/policies/status_command_policy.ts) · [`src/application/usecases/steps/common/publish_resume_workflow.ts`](../src/application/usecases/steps/common/publish_resume_workflow.ts) · [`src/application/usecases/steps/commit/bugbot/schema.ts`](../src/application/usecases/steps/commit/bugbot/schema.ts) · [`src/application/usecases/steps/commit/bugbot/prepare_bugbot_findings_policy.ts`](../src/application/usecases/steps/commit/bugbot/prepare_bugbot_findings_policy.ts) · [`src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts`](../src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts) -- Tests: [`src/application/usecases/__tests__/commit_use_case.test.ts`](../src/application/usecases/__tests__/commit_use_case.test.ts) · [`src/domain/bugbot/__tests__/review_state.test.ts`](../src/domain/bugbot/__tests__/review_state.test.ts) · [`src/domain/bugbot/__tests__/review_projection.test.ts`](../src/domain/bugbot/__tests__/review_projection.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/schema.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/schema.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/prepare_bugbot_findings.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/prepare_bugbot_findings.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/reconcile_bugbot_review_state_integration.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/reconcile_bugbot_review_state_integration.test.ts) · [`src/application/policies/__tests__/bugbot_result_finding_state_projection_policy.test.ts`](../src/application/policies/__tests__/bugbot_result_finding_state_projection_policy.test.ts) · [`src/application/policies/__tests__/bugbot_telemetry_projection_policy.test.ts`](../src/application/policies/__tests__/bugbot_telemetry_projection_policy.test.ts) · [`src/application/policies/__tests__/action_summary_policy.test.ts`](../src/application/policies/__tests__/action_summary_policy.test.ts) · [`src/application/policies/__tests__/copilot_evidence_policy.test.ts`](../src/application/policies/__tests__/copilot_evidence_policy.test.ts) · [`src/application/policies/__tests__/lifecycle_state_policy.test.ts`](../src/application/policies/__tests__/lifecycle_state_policy.test.ts) · [`src/application/policies/__tests__/status_command_policy.test.ts`](../src/application/policies/__tests__/status_command_policy.test.ts) · [`src/actions/__tests__/github_action_completion.test.ts`](../src/actions/__tests__/github_action_completion.test.ts) · [`src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts`](../src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts) · [`src/application/usecases/actions/__tests__/synchronize_lifecycle_state_use_case.test.ts`](../src/application/usecases/actions/__tests__/synchronize_lifecycle_state_use_case.test.ts) · [`src/tooling/__tests__/validate_workflow_contract.test.ts`](../src/tooling/__tests__/validate_workflow_contract.test.ts) +- Entrypoints: [`src/application/usecases/steps/commit/detect_potential_problems_workflow.ts`](../src/application/usecases/steps/commit/detect_potential_problems_workflow.ts) · [`src/application/usecases/steps/commit/bugbot/reconcile_bugbot_review_state_use_case.ts`](../src/application/usecases/steps/commit/bugbot/reconcile_bugbot_review_state_use_case.ts) · [`src/actions/github_action_completion.ts`](../src/actions/github_action_completion.ts) +- Core code: [`src/domain/bugbot/review_state.ts`](../src/domain/bugbot/review_state.ts) · [`src/domain/bugbot/review_projection.ts`](../src/domain/bugbot/review_projection.ts) · [`src/application/policies/bugbot_reconciliation_policy.ts`](../src/application/policies/bugbot_reconciliation_policy.ts) · [`src/application/policies/bugbot_event_ownership_policy.ts`](../src/application/policies/bugbot_event_ownership_policy.ts) · [`src/application/policies/bugbot_result_finding_state_projection_policy.ts`](../src/application/policies/bugbot_result_finding_state_projection_policy.ts) · [`src/application/policies/bugbot_telemetry_projection_policy.ts`](../src/application/policies/bugbot_telemetry_projection_policy.ts) · [`src/application/policies/action_summary_policy.ts`](../src/application/policies/action_summary_policy.ts) · [`src/application/policies/copilot_evidence_policy.ts`](../src/application/policies/copilot_evidence_policy.ts) · [`src/application/policies/lifecycle_state_policy.ts`](../src/application/policies/lifecycle_state_policy.ts) · [`src/application/policies/status_command_policy.ts`](../src/application/policies/status_command_policy.ts) · [`src/application/usecases/steps/common/publish_resume_workflow.ts`](../src/application/usecases/steps/common/publish_resume_workflow.ts) · [`src/application/usecases/steps/commit/bugbot/load_bugbot_context_use_case.ts`](../src/application/usecases/steps/commit/bugbot/load_bugbot_context_use_case.ts) · [`src/application/usecases/steps/commit/bugbot/schema.ts`](../src/application/usecases/steps/commit/bugbot/schema.ts) · [`src/application/usecases/steps/commit/bugbot/prepare_bugbot_findings_policy.ts`](../src/application/usecases/steps/commit/bugbot/prepare_bugbot_findings_policy.ts) · [`src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts`](../src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts) +- Tests: [`src/domain/bugbot/__tests__/review_state.test.ts`](../src/domain/bugbot/__tests__/review_state.test.ts) · [`src/domain/bugbot/__tests__/review_projection.test.ts`](../src/domain/bugbot/__tests__/review_projection.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/schema.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/schema.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/prepare_bugbot_findings.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/prepare_bugbot_findings.test.ts) · [`src/application/usecases/steps/commit/bugbot/__tests__/reconcile_bugbot_review_state_integration.test.ts`](../src/application/usecases/steps/commit/bugbot/__tests__/reconcile_bugbot_review_state_integration.test.ts) · [`src/application/policies/__tests__/bugbot_result_finding_state_projection_policy.test.ts`](../src/application/policies/__tests__/bugbot_result_finding_state_projection_policy.test.ts) · [`src/application/policies/__tests__/bugbot_event_ownership_policy.test.ts`](../src/application/policies/__tests__/bugbot_event_ownership_policy.test.ts) · [`src/application/policies/__tests__/bugbot_telemetry_projection_policy.test.ts`](../src/application/policies/__tests__/bugbot_telemetry_projection_policy.test.ts) · [`src/application/policies/__tests__/action_summary_policy.test.ts`](../src/application/policies/__tests__/action_summary_policy.test.ts) · [`src/application/policies/__tests__/copilot_evidence_policy.test.ts`](../src/application/policies/__tests__/copilot_evidence_policy.test.ts) · [`src/application/policies/__tests__/lifecycle_state_policy.test.ts`](../src/application/policies/__tests__/lifecycle_state_policy.test.ts) · [`src/application/policies/__tests__/status_command_policy.test.ts`](../src/application/policies/__tests__/status_command_policy.test.ts) · [`src/actions/__tests__/github_action_completion.test.ts`](../src/actions/__tests__/github_action_completion.test.ts) · [`src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts`](../src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts) · [`src/application/usecases/actions/__tests__/synchronize_lifecycle_state_use_case.test.ts`](../src/application/usecases/actions/__tests__/synchronize_lifecycle_state_use_case.test.ts) · [`src/application/usecases/steps/commit/__tests__/detect_potential_problems_use_case.test.ts`](../src/application/usecases/steps/commit/__tests__/detect_potential_problems_use_case.test.ts) · [`src/tooling/__tests__/validate_workflow_contract.test.ts`](../src/tooling/__tests__/validate_workflow_contract.test.ts) - User documentation: [`docs/bugbot/detection.mdx`](../docs/bugbot/detection.mdx) · [`docs/bugbot/finding-publication.mdx`](../docs/bugbot/finding-publication.mdx) · [`docs/bugbot/quality-observability.mdx`](../docs/bugbot/quality-observability.mdx) · [`docs/bugbot/failure-scenarios.mdx`](../docs/bugbot/failure-scenarios.mdx) · [`docs/bugbot/configuration.mdx`](../docs/bugbot/configuration.mdx) · [`docs/bugbot/how-it-works.mdx`](../docs/bugbot/how-it-works.mdx) · [`docs/pull-requests/workflow-setup.mdx`](../docs/pull-requests/workflow-setup.mdx) ### `execution-lifecycle` — Execution admission, queueing, routing, and result publication diff --git a/specs/bugbot-review-state-reconciliation.md b/specs/bugbot-review-state-reconciliation.md index 9db16c8a3..50c3ab061 100644 --- a/specs/bugbot-review-state-reconciliation.md +++ b/specs/bugbot-review-state-reconciliation.md @@ -176,9 +176,12 @@ canceled Commit run `34846885692` through the same native group after every job step had succeeded, leaving a misleading canceled conclusion. Running Bugbot from both event routes also made cancellation necessary only because ownership was duplicated. The final contract therefore uses distinct `copilot-push-…` -and `copilot-pr-…` branch groups. When Commit discovers an open same-repository -PR, it retains issue progress work but skips Bugbot; the PR synchronization -event exclusively owns review for that head. PR metadata retains conditional +and `copilot-pr-…` branch groups. The Commit route retains issue progress work, +then Bugbot's read-only exact-head preflight validates whether an open +same-repository PR owns the pushed branch. A match skips review-context loading +and agent invocation; the PR synchronization event exclusively owns review for +that head. This decision uses provider discovery rather than the absent +`pull_request` field on a push payload. PR metadata retains conditional non-preemption within the PR-specific group, and fork PR execution remains excluded by the same-repository workflow gate. @@ -471,9 +474,11 @@ readiness. `unknown` is a system failure and fails the review regardless of MUST NOT mutate findings or current-state projections. - Shipped Commit and Pull Request workflows MUST use distinct branch-scoped concurrency groups. Each uses cancel-in-progress semantics only for its own - replaceable revisions. After Commit discovers an open same-repository PR, it - skips Bugbot and the PR code-change event exclusively owns review for that - head. `pull_request: edited` uses the PR group with cancellation disabled, so + replaceable revisions. On push, a read-only exact-head preflight MUST validate + any open same-repository PR before Bugbot loads review context or invokes the + agent; a validated match yields to the PR code-change event. The decision MUST + NOT read PR identity from the push payload. `pull_request: edited` uses the PR + group with cancellation disabled, so it waits and cannot preempt an active review. Application freshness checks remain mandatory because API consumers and comment-triggered flows are not fully serialized by workflow YAML. @@ -1322,7 +1327,7 @@ examples should reuse the same fixtures as presentation tests where practical. | Safe resolution/reopen order | reconciliation plan/apply use case | every partial mutation boundary | How it works, Failure scenarios | | Human dismissal precedence | lifecycle policy + resolver adapter | bot/human/unknown resolver matrix | Concepts, Detection | | Freshness and concurrency | head guards + workflow contract | stale, duplicate, canceled, race cases | Workflow setup | -| Review ownership and metadata non-preemption | distinct push/PR branch groups + open-PR Bugbot omission + conditional PR cancellation | active/setup workflow parser, cross-group negative fixtures, Commit route test, PR #363/#367 live evidence | Workflow setup, Configuration, How it works | +| Review ownership and metadata non-preemption | distinct push/PR branch groups + exact-head push preflight + conditional PR cancellation | active/setup workflow parser, cross-group negative fixtures, ownership policy matrix, push-shaped detection integration, PR #363/#367 live evidence | Workflow setup, Configuration, How it works | | Metadata/review publication ownership | result-publication mode + telemetry/evidence policies | metadata generic-comment negative, outcome matrix, completion integration, PR #363 latest-by-name/noise replay | Detection, Workflow setup, How it works, Troubleshooting | | Canonical Result evidence | discriminated telemetry-set and finding-state projections + completion/lifecycle/status/summary/Check policies | complete aggregation, malformed/duplicate telemetry siblings, required-outcome absence, missing/extra key, numeric limits, overflow, cross-surface fail-closed cases | Detection, Observability, Comment commands, Failure scenarios | | Coherent final snapshot | snapshot loader + explicit surface completeness | before/after head, head-change, missing-head, per-surface failure, shared issue/PR read cases | How it works, Failure scenarios | diff --git a/specs/catalog.json b/specs/catalog.json index d42329f0a..5c76881da 100644 --- a/specs/catalog.json +++ b/specs/catalog.json @@ -214,7 +214,7 @@ ".github/workflows/copilot_pull_request_comment.yml" ], "entrypoints": [ - "src/application/usecases/commit_use_case.ts", + "src/application/usecases/steps/commit/detect_potential_problems_workflow.ts", "src/application/usecases/steps/commit/bugbot/reconcile_bugbot_review_state_use_case.ts", "src/actions/github_action_completion.ts" ], @@ -222,6 +222,7 @@ "src/domain/bugbot/review_state.ts", "src/domain/bugbot/review_projection.ts", "src/application/policies/bugbot_reconciliation_policy.ts", + "src/application/policies/bugbot_event_ownership_policy.ts", "src/application/policies/bugbot_result_finding_state_projection_policy.ts", "src/application/policies/bugbot_telemetry_projection_policy.ts", "src/application/policies/action_summary_policy.ts", @@ -229,18 +230,19 @@ "src/application/policies/lifecycle_state_policy.ts", "src/application/policies/status_command_policy.ts", "src/application/usecases/steps/common/publish_resume_workflow.ts", + "src/application/usecases/steps/commit/bugbot/load_bugbot_context_use_case.ts", "src/application/usecases/steps/commit/bugbot/schema.ts", "src/application/usecases/steps/commit/bugbot/prepare_bugbot_findings_policy.ts", "src/application/usecases/steps/commit/bugbot/synchronize_bugbot_review_presentation_use_case.ts" ], "tests": [ - "src/application/usecases/__tests__/commit_use_case.test.ts", "src/domain/bugbot/__tests__/review_state.test.ts", "src/domain/bugbot/__tests__/review_projection.test.ts", "src/application/usecases/steps/commit/bugbot/__tests__/schema.test.ts", "src/application/usecases/steps/commit/bugbot/__tests__/prepare_bugbot_findings.test.ts", "src/application/usecases/steps/commit/bugbot/__tests__/reconcile_bugbot_review_state_integration.test.ts", "src/application/policies/__tests__/bugbot_result_finding_state_projection_policy.test.ts", + "src/application/policies/__tests__/bugbot_event_ownership_policy.test.ts", "src/application/policies/__tests__/bugbot_telemetry_projection_policy.test.ts", "src/application/policies/__tests__/action_summary_policy.test.ts", "src/application/policies/__tests__/copilot_evidence_policy.test.ts", @@ -249,6 +251,7 @@ "src/actions/__tests__/github_action_completion.test.ts", "src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts", "src/application/usecases/actions/__tests__/synchronize_lifecycle_state_use_case.test.ts", + "src/application/usecases/steps/commit/__tests__/detect_potential_problems_use_case.test.ts", "src/tooling/__tests__/validate_workflow_contract.test.ts" ], "documentation": [ diff --git a/specs/issue-and-pull-request-context-hardening.md b/specs/issue-and-pull-request-context-hardening.md index bd59296e5..c4cc5ac41 100644 --- a/specs/issue-and-pull-request-context-hardening.md +++ b/specs/issue-and-pull-request-context-hardening.md @@ -31,8 +31,11 @@ URLs MUST never be fetched. PR metadata normalization MUST share the PR-specific branch serialization boundary without preempting an active code review. Commit uses a distinct -push-specific boundary, and once it discovers an open same-repository PR it -MUST skip Bugbot so PR synchronization exclusively owns review for that head. +push-specific boundary. Its Bugbot path MUST perform a provider-backed, +exact-head, same-repository preflight and stop before review-context loading or +agent invocation when that selection proves an open PR exists. It MUST NOT infer +PR ownership from the push payload. PR synchronization then exclusively owns +review for that head. A newer PR review event MAY cancel an obsolete PR review; `pull_request: edited` MUST wait and MUST NOT replace a real `synchronize` analysis with a green metadata-only run. After it waits, the @@ -672,7 +675,7 @@ SDDs, catalog metadata, generated catalog, and bundles are updated together. | safe PR link | exact-target adapter and compensation workflow | forged URL, replay, marker, every failure edge | PR capabilities, troubleshooting | | clean cut and ceiling | AST ratchet, typecheck, generated bundles | zero-leaf/old-symbol negative fixtures | semantic context SDD | | UX/operations | semantic result strings and common publisher | state/retained-action assertions + manual review | PR and troubleshooting pages | -| review-preserving ownership | distinct push/PR branch groups + conditional PR cancellation + open-PR Bugbot omission | workflow validator, cross-group negative fixtures, Commit route test, PR #363/#367 live sequence | workflow setup, Bugbot configuration/how-it-works | +| review-preserving ownership | distinct push/PR branch groups + conditional PR cancellation + exact-head push preflight | workflow validator, cross-group negative fixtures, ownership policy matrix, push-shaped detection integration, PR #363/#367 live sequence | workflow setup, Bugbot configuration/how-it-works | | review publication ownership | result-publication mode + discriminated Bugbot telemetry/evidence policies | metadata-only comment/Check negatives, malformed-sibling cross-consumer cases, partial/complete/skipped policy cases, action-completion integration, PR #363 latest-by-name/noise sequence | workflow setup, Bugbot detection/how-it-works, troubleshooting | ## 18. Implementation sequence @@ -741,8 +744,9 @@ SDDs, catalog metadata, generated catalog, and bundles are updated together. - Decision: one description request with an explicit trigger replaces two entry methods; rejected overloads and deprecated aliases. - Decision: use distinct push and PR branch concurrency keys, make PR - synchronization the sole Bugbot owner after an open same-repository PR is - discovered, and conditionally disable PR-key preemption for + synchronization the sole Bugbot owner after a provider-backed exact-head + preflight discovers an open same-repository PR, and conditionally disable + PR-key preemption for `pull_request: edited`. PR #363 proved unconditional metadata cancellation can hide a missed review; PR #367 proved the shared push/PR key can cancel useful completed push work. Separate event lanes are safe because only the PR lane diff --git a/specs/semantic-github-publication-and-notification.md b/specs/semantic-github-publication-and-notification.md index 6ea887f7a..b4a75d539 100644 --- a/specs/semantic-github-publication-and-notification.md +++ b/specs/semantic-github-publication-and-notification.md @@ -391,12 +391,15 @@ groups keyed by repository and branch. A paired `push` and cancel only an older replaceable run from the same workflow; PR metadata-only `edited` events MUST continue to queue without preempting an active PR review. -After the Commit route discovers an open same-repository PR for its branch, it -MUST retain native issue state, size, and progress work but MUST omit Bugbot. -The PR `synchronize` route then exclusively owns Bugbot review for that head. -An issue-linked branch without an open PR retains push-time Bugbot. Fork PR -workflows remain outside this contract and MUST stay excluded by the existing -same-repository admission gate. +The Commit route MUST retain native issue state, size, and progress work, then +invoke a read-only Bugbot preflight that resolves the pushed branch through the +provider's exact-head lookup. If that validated selection is an open PR in the +same base repository, the push route MUST stop before loading review context or +invoking the agent. The PR `synchronize` route then exclusively owns Bugbot +review for that head. The decision MUST NOT depend on a `pull_request` field in +the `push` payload. An issue-linked branch without an open PR retains push-time +Bugbot. Fork PR workflows remain outside this contract and MUST stay excluded +by the existing same-repository admission gate. ### 6.6 State machine @@ -1051,7 +1054,7 @@ removed. | §6.2 capability behavior | capability outcome adapters/coordinator | route and end-to-end cases | issue/PR/release/Bugbot pages | | §6.4 one canonical card | owned query/mutation ports and reconciler | duplicate/race/pagination tests | operator recovery guide | | §6.5 freshness | source guard and feature revisions | stale/out-of-order/replay tests | observability guide | -| §6.5.1 event ownership | distinct push/PR groups + open-PR Bugbot omission | workflow contract, route unit test, PR #367 paired-event evidence | workflow setup and Bugbot guides | +| §6.5.1 event ownership | distinct push/PR groups + exact-head preflight and ownership policy | workflow contract, push-shaped preflight integration test, policy matrix, PR #367 paired-event evidence | workflow setup and Bugbot guides | | §7 quiet/image defaults | action/setup configuration policies | action schema, setup, doctor, migration tests | configuration and upgrade pages | | §8 clean boundaries | architecture and source-inventory checks | executable boundary tests | contributor architecture | | §9 message hierarchy/examples | localized feature renderers | semantic golden fixtures and manual UX matrix | user journeys | diff --git a/src/application/policies/__tests__/bugbot_event_ownership_policy.test.ts b/src/application/policies/__tests__/bugbot_event_ownership_policy.test.ts new file mode 100644 index 000000000..ce3e8c92c --- /dev/null +++ b/src/application/policies/__tests__/bugbot_event_ownership_policy.test.ts @@ -0,0 +1,41 @@ +import type { BugbotPullRequestIdentity } from '../../../domain/bugbot/context'; +import { selectPullRequestOwnerForPushReview } from '../bugbot_event_ownership_policy'; + +const pullRequest: BugbotPullRequestIdentity = { + number: 42, + state: 'open', + baseRepository: { owner: 'owner', name: 'repo' }, + headRepositoryOwner: 'owner', + headRef: 'feature/42', + headSha: 'a'.repeat(40), +}; + +describe('Bugbot event ownership policy', () => { + it('yields an automatic push review after exact-head PR verification', () => { + expect(selectPullRequestOwnerForPushReview({ + triggerKind: 'push', + eventTargetsPullRequest: false, + selectionReason: 'exact-head', + canonicalPullRequest: pullRequest, + })).toEqual(pullRequest); + }); + + it.each([ + ['push without an open PR', 'push', false, 'none', null], + ['pull request event', 'pull_request', true, 'event', pullRequest], + ['on-demand review', 'workflow_dispatch', false, 'exact-head', pullRequest], + ] as const)('keeps review ownership for %s', ( + _scenario, + triggerKind, + eventTargetsPullRequest, + selectionReason, + canonicalPullRequest, + ) => { + expect(selectPullRequestOwnerForPushReview({ + triggerKind, + eventTargetsPullRequest, + selectionReason, + canonicalPullRequest, + })).toBeNull(); + }); +}); diff --git a/src/application/policies/bugbot_event_ownership_policy.ts b/src/application/policies/bugbot_event_ownership_policy.ts new file mode 100644 index 000000000..b9bbcd766 --- /dev/null +++ b/src/application/policies/bugbot_event_ownership_policy.ts @@ -0,0 +1,22 @@ +import type { BugbotPullRequestIdentity } from '../../domain/bugbot/context'; + +export interface BugbotEventOwnershipContext { + readonly triggerKind: string; + readonly eventTargetsPullRequest: boolean; + readonly selectionReason: 'event' | 'exact-head' | 'none'; + readonly canonicalPullRequest: BugbotPullRequestIdentity | null; +} + +/** + * Automatic push review yields only after an exact-head provider lookup proves + * that an open same-repository pull request owns the branch revision. + */ +export function selectPullRequestOwnerForPushReview( + context: BugbotEventOwnershipContext, +): BugbotPullRequestIdentity | null { + const pullRequestOwnsReview = context.triggerKind === 'push' + && !context.eventTargetsPullRequest + && context.selectionReason === 'exact-head' + && context.canonicalPullRequest !== null; + return pullRequestOwnsReview ? context.canonicalPullRequest : null; +} diff --git a/src/application/usecases/__tests__/commit_use_case.test.ts b/src/application/usecases/__tests__/commit_use_case.test.ts index 3bd59abd4..869b58ff8 100644 --- a/src/application/usecases/__tests__/commit_use_case.test.ts +++ b/src/application/usecases/__tests__/commit_use_case.test.ts @@ -153,25 +153,6 @@ describe('CommitUseCase', () => { expect(results[1].id).toBe('c'); }); - it('keeps push metadata and progress but lets an open pull request own Bugbot review', async () => { - const useCase = new CommitUseCase( - { invoke: mockNotifyInvoke } as any, - { invoke: mockCheckChangesInvoke } as any, - { invoke: mockDetectProblemsInvoke } as any, - { invoke: mockCheckProgressInvoke } as any, - ); - const param = minimalExecution({ - pullRequest: { number: 42, head: 'feature/123', action: '' }, - }); - - await useCase.invoke(param); - - expect(mockNotifyInvoke).toHaveBeenCalledTimes(1); - expect(mockCheckChangesInvoke).toHaveBeenCalledTimes(1); - expect(mockCheckProgressInvoke).toHaveBeenCalledTimes(1); - expect(mockDetectProblemsInvoke).not.toHaveBeenCalled(); - }); - it('on error pushes failure result and rethrows', async () => { mockNotifyInvoke.mockRejectedValue(new Error('step failed')); diff --git a/src/application/usecases/commit_use_case.ts b/src/application/usecases/commit_use_case.ts index f3f77504f..0fe3024a7 100644 --- a/src/application/usecases/commit_use_case.ts +++ b/src/application/usecases/commit_use_case.ts @@ -52,15 +52,9 @@ export class CommitUseCase implements ParamUseCase { )); if (agentAllowed) { results.push(...(await this.checkProgressUseCase.invoke(projectProgressContext(param)))); - if (param.pullRequest.number > 0) { - logInfo( - `Skipping push Bugbot analysis because pull request #${param.pullRequest.number} owns review for this head.`, - ); - } else { - results.push(...(await this.detectPotentialProblemsUseCase.invoke( - projectBugbotReviewOperationContext(param), - ))); - } + results.push(...(await this.detectPotentialProblemsUseCase.invoke( + projectBugbotReviewOperationContext(param), + ))); } else { logInfo('Skipping push agent analysis because ai-members-only is enabled and the actor is not authorized.'); } diff --git a/src/application/usecases/steps/commit/__tests__/detect_potential_problems_use_case.test.ts b/src/application/usecases/steps/commit/__tests__/detect_potential_problems_use_case.test.ts index 5d8337800..2cee8dee0 100644 --- a/src/application/usecases/steps/commit/__tests__/detect_potential_problems_use_case.test.ts +++ b/src/application/usecases/steps/commit/__tests__/detect_potential_problems_use_case.test.ts @@ -51,6 +51,7 @@ const mockListPullRequestReviews = jest.fn(); const mockUpdatePullRequestReview = jest.fn(); const mockAskAgent = jest.fn(); +const mockPublishTelemetry = jest.fn(); let issueCoverageOverride: BugbotSourceCoverage | undefined; function completeCoverage(source: BugbotContextSource, items: number) { @@ -286,6 +287,7 @@ describe("DetectPotentialProblemsUseCase", () => { }, }, }, + { publish: mockPublishTelemetry }, ); mockListIssueComments.mockReset(); mockAddComment.mockReset(); @@ -313,6 +315,7 @@ describe("DetectPotentialProblemsUseCase", () => { mockListPullRequestReviews.mockReset().mockResolvedValue([]); mockUpdatePullRequestReview.mockReset().mockResolvedValue(undefined); mockAskAgent.mockReset(); + mockPublishTelemetry.mockReset(); issueCoverageOverride = undefined; mockListIssueComments.mockResolvedValue([]); @@ -368,6 +371,33 @@ describe("DetectPotentialProblemsUseCase", () => { expect(mockAskAgent).not.toHaveBeenCalled(); }); + it('lets the PR event own review when a push preflight discovers an exact-head open PR', async () => { + mockFindExactHeadCandidateNumbers.mockResolvedValue([17]); + mockGetPullRequestHeadSha.mockResolvedValue('a'.repeat(40)); + const param = baseParam({ + eventName: 'push', + inputs: { before: 'b'.repeat(40), after: 'a'.repeat(40) }, + }); + + const results = await invokeUseCase(useCase, param); + + expect(results).toEqual([]); + expect(mockFindExactHeadCandidateNumbers).toHaveBeenCalledWith('feature/42-add-feature'); + expect(mockListIssueComments).not.toHaveBeenCalled(); + expect(mockListPullRequestReviewComments).not.toHaveBeenCalled(); + expect(mockGetReviewDiffSnapshot).not.toHaveBeenCalled(); + expect(mockAskAgent).not.toHaveBeenCalled(); + expect(mockPublishTelemetry).toHaveBeenCalledWith(expect.objectContaining({ + outcome: 'skipped', + errorCategory: 'pull_request_ownership', + pullRequestNumber: 17, + contextSelectionReason: 'exact-head', + contextCandidateBucket: '1', + contextLogicalProviderReads: 1, + contextRawProviderRequests: 1, + })); + }); + it('skips draft pull requests when draft reviews are disabled', async () => { const results = await invokeUseCase(useCase, baseParam({ issueNumber: -1, diff --git a/src/application/usecases/steps/commit/bugbot/__tests__/load_bugbot_context_use_case.test.ts b/src/application/usecases/steps/commit/bugbot/__tests__/load_bugbot_context_use_case.test.ts index 73a9ebadb..f360f9ded 100644 --- a/src/application/usecases/steps/commit/bugbot/__tests__/load_bugbot_context_use_case.test.ts +++ b/src/application/usecases/steps/commit/bugbot/__tests__/load_bugbot_context_use_case.test.ts @@ -5,7 +5,10 @@ import type { BugbotPullRequestIdentity, BugbotSourceCoverage, } from '../../../../../../domain/bugbot/context'; -import { loadBugbotContext } from '../load_bugbot_context_use_case'; +import { + loadBugbotContext, + preflightBugbotContext, +} from '../load_bugbot_context_use_case'; import type { BugbotContextRequest } from '../bugbot_context_request'; jest.mock('../../../../../../utils/logger', () => ({ logDebugInfo: jest.fn() })); @@ -248,7 +251,9 @@ describe('loadBugbotContext', () => { filesWithDiffLocations: [], }, 'diff')), }); - const pending = loadBugbotContext(request(), reader); + const contextRequest = request(); + const preflight = await preflightBugbotContext(contextRequest, reader); + const pending = loadBugbotContext(contextRequest, reader, preflight); await Promise.resolve(); await Promise.resolve(); expect(maximum).toBe(2); diff --git a/src/application/usecases/steps/commit/bugbot/bugbot_review_telemetry.ts b/src/application/usecases/steps/commit/bugbot/bugbot_review_telemetry.ts index 645126822..728d9a8dd 100644 --- a/src/application/usecases/steps/commit/bugbot/bugbot_review_telemetry.ts +++ b/src/application/usecases/steps/commit/bugbot/bugbot_review_telemetry.ts @@ -4,6 +4,7 @@ import type { PreparedBugbotFindings } from './prepare_bugbot_findings'; import { projectBugbotFindingStatuses } from '../../../../policies/bugbot_finding_status_policy'; import type { BugbotReviewProjection } from '../../../../../domain/bugbot/review_projection'; import type { BugbotReviewOperationContext } from './bugbot_review_operation_context'; +import type { BugbotContextPreflight } from './load_bugbot_context_use_case'; export interface BugbotReviewTelemetryClock { now(): number; @@ -21,6 +22,7 @@ export class BugbotReviewTelemetry { private readonly stages: Record = {}; private promptCharacters = 0; private responseCharacters = 0; + private preflight?: BugbotContextPreflight; private context?: BugbotContext; private prepared?: PreparedBugbotFindings; private projection?: BugbotReviewProjection; @@ -42,6 +44,10 @@ export class BugbotReviewTelemetry { } } + observePreflight(preflight: BugbotContextPreflight): void { + this.preflight = preflight; + } + observeContext(context: BugbotContext, prompt: string): void { this.context = context; this.promptCharacters = prompt.length; @@ -62,8 +68,11 @@ export class BugbotReviewTelemetry { snapshot(outcome: BugbotReviewOutcome, errorCategory?: string): BugbotReviewTelemetrySnapshot { const changes = this.context?.prContext?.changes ?? []; - const headSha = this.context?.prContext?.prHeadSha; - const canonicalPullRequestNumber = this.context?.canonicalPullRequest?.number; + const canonicalPullRequest = this.context?.canonicalPullRequest + ?? this.preflight?.canonicalPullRequest; + const headSha = this.context?.prContext?.prHeadSha + ?? canonicalPullRequest?.headSha; + const canonicalPullRequestNumber = canonicalPullRequest?.number; const contextCoverage = Object.fromEntries( (this.context?.coverage.sources ?? []) .map((source) => [source.source, { @@ -77,7 +86,9 @@ export class BugbotReviewTelemetry { ...(source.providerLimitReached ? { providerLimitReached: true } : {}), }]), ); - const providerSources = (this.context?.coverage.sources ?? []).filter((source) => + const observedSources = this.context?.coverage.sources + ?? (this.preflight ? [this.preflight.selectionCoverage] : []); + const providerSources = observedSources.filter((source) => source.pagesFetched > 0 && [ 'selection', 'issue-comments', @@ -85,8 +96,10 @@ export class BugbotReviewTelemetry { 'review-threads', 'diff', ].includes(source.source)); - const selectionCandidates = this.context?.coverage.sources + const selectionCandidates = observedSources .find((source) => source.source === 'selection')?.itemsFetched; + const contextSelectionReason = this.context?.selectionReason + ?? this.preflight?.selectionReason; const repositoryId = this.execution.repository.id; const startedAtEpoch = Date.parse(this.startedAt); const reviewId = [ @@ -136,11 +149,13 @@ export class BugbotReviewTelemetry { changedFiles: changes.length, changedLines: changes.reduce((sum, change) => sum + change.additions + change.deletions, 0), rulesLoaded: this.context?.reviewRuleSources?.length ?? 0, - ...(this.context ? { - contextSelectionReason: this.context.selectionReason, + ...(contextSelectionReason ? { + contextSelectionReason, ...(selectionCandidates !== undefined ? { contextCandidateBucket: selectionCandidates >= 2 ? '2+' as const : String(selectionCandidates) as '0' | '1', } : {}), + } : {}), + ...(this.context ? { contextCoverageStatus: this.context.coverage.status, contextCoverage, } : {}), diff --git a/src/application/usecases/steps/commit/bugbot/load_bugbot_context_use_case.ts b/src/application/usecases/steps/commit/bugbot/load_bugbot_context_use_case.ts index bc47a68cd..3485af2d1 100644 --- a/src/application/usecases/steps/commit/bugbot/load_bugbot_context_use_case.ts +++ b/src/application/usecases/steps/commit/bugbot/load_bugbot_context_use_case.ts @@ -29,17 +29,37 @@ type LoadedSource = | { readonly kind: "threads"; readonly value: Readonly>; readonly coverage: BugbotSourceCoverage } | { readonly kind: "diff"; readonly value: import("../../../../ports/bugbot_pull_request_read_ports").PullRequestReviewDiffSnapshot; readonly coverage: BugbotSourceCoverage }; -export async function loadBugbotContext( +export interface BugbotContextPreflight { + readonly canonicalPullRequest: BugbotPullRequestIdentity | null; + readonly selectionReason: 'event' | 'exact-head' | 'none'; + readonly selectionCoverage: BugbotSourceCoverage; +} + +/** Resolves and validates the provider-owned PR identity without loading review context. */ +export async function preflightBugbotContext( request: BugbotContextRequest, ports: BoundBugbotContextReadPorts, -): Promise { +): Promise { const selection = await selectCanonicalPullRequest(request, ports); const canonicalPullRequest = requireUsableSelection(request, selection); - const selectionCoverage = completeBugbotSourceCoverage( - "selection", - selection.kind === "canonical" ? 1 : selection.kind === "ambiguous" ? 2 : 0, - selectionPageCount(request), - ); + return { + canonicalPullRequest, + selectionReason: selection.kind === 'canonical' ? selection.reason : 'none', + selectionCoverage: completeBugbotSourceCoverage( + 'selection', + selection.kind === 'canonical' ? 1 : selection.kind === 'ambiguous' ? 2 : 0, + selectionPageCount(request), + ), + }; +} + +export async function loadBugbotContext( + request: BugbotContextRequest, + ports: BoundBugbotContextReadPorts, + resolvedPreflight?: BugbotContextPreflight, +): Promise { + const preflight = resolvedPreflight ?? await preflightBugbotContext(request, ports); + const { canonicalPullRequest, selectionCoverage, selectionReason } = preflight; const tasks: Array<() => Promise> = []; if (request.target.issueNumber !== undefined) { tasks.push(async () => { @@ -135,13 +155,13 @@ export async function loadBugbotContext( }, ]); logDebugInfo( - `LoadBugbotContext: selection=${selection.kind}, coverage=${coverage.status}, existing findings=${Object.keys(parsedComments.existingByFindingId).length}, retained previous findings=${previousContext.selected.length}, diff files=${prContext?.changes?.length ?? 0}.`, + `LoadBugbotContext: selection=${selectionReason}, coverage=${coverage.status}, existing findings=${Object.keys(parsedComments.existingByFindingId).length}, retained previous findings=${previousContext.selected.length}, diff files=${prContext?.changes?.length ?? 0}.`, ); return { existingByFindingId: parsedComments.existingByFindingId, issueComments: parsedComments.issueComments, canonicalPullRequest, - selectionReason: selection.kind === "canonical" ? selection.reason : "none", + selectionReason, coverage, eligibleResolutionIds: new Set(previousContext.selected.map((finding) => finding.id)), previousFindingsBlock: previousContext.block, diff --git a/src/application/usecases/steps/commit/detect_potential_problems_workflow.ts b/src/application/usecases/steps/commit/detect_potential_problems_workflow.ts index c99b26f2c..59a9a2d69 100644 --- a/src/application/usecases/steps/commit/detect_potential_problems_workflow.ts +++ b/src/application/usecases/steps/commit/detect_potential_problems_workflow.ts @@ -5,7 +5,10 @@ import { getTaskEmoji } from '../../../../utils/task_emoji'; import { logDebugInfo, logError, logInfo } from '../../../ports/logging_ports'; import type { BugbotScmPorts } from '../../../ports/bugbot_scm_ports'; import { PullRequestReviewOperationError } from '../../../ports/pull_request_review_errors'; -import { loadBugbotContext } from './bugbot/load_bugbot_context_use_case'; +import { + loadBugbotContext, + preflightBugbotContext, +} from './bugbot/load_bugbot_context_use_case'; import { projectBugbotContextRequest, type LoadBugbotContextOptions, @@ -27,6 +30,7 @@ import { ApplicationError, toApplicationError } from '../../../errors/applicatio import { type BugbotReviewOperationContext, } from './bugbot/bugbot_review_operation_context'; +import { selectPullRequestOwnerForPushReview } from '../../../policies/bugbot_event_ownership_policy'; export interface DetectPotentialProblemsWorkflowDependencies { aiRepository: FindingsQueryPort; @@ -80,8 +84,24 @@ export async function runDetectPotentialProblemsWorkflow( return []; } const contextRequest = projectBugbotContextRequest(reviewContext, contextOptions); + const preflight = await telemetry.measure('context-preflight', () => + preflightBugbotContext(contextRequest, dependencies.scm.context)); + telemetry.observePreflight(preflight); + const owningPullRequest = selectPullRequestOwnerForPushReview({ + triggerKind: reviewContext.trigger.kind, + eventTargetsPullRequest: reviewContext.target.isPullRequest, + selectionReason: preflight.selectionReason, + canonicalPullRequest: preflight.canonicalPullRequest, + }); + if (owningPullRequest) { + logInfo( + `Skipping push Bugbot analysis because pull request #${owningPullRequest.number} owns review for this head.`, + ); + await publishTelemetry('skipped', 'pull_request_ownership'); + return []; + } const context = await telemetry.measure('context', () => - loadBugbotContext(contextRequest, dependencies.scm.context)); + loadBugbotContext(contextRequest, dependencies.scm.context, preflight)); const eventHeadSha = reviewContext.trigger.expectedHeadSha; if (isLoadedBugbotRevisionSuperseded(context, eventHeadSha)) { return await complete(supersededResult(context.prContext?.prHeadSha, eventHeadSha), 'superseded'); From 5187b7f2f79235d7f19c48601a81a3b873a8f5d8 Mon Sep 17 00:00:00 2001 From: Efra Espada Date: Mon, 14 Sep 2026 15:49:24 +0200 Subject: [PATCH 6/6] codex-semantic-github-publication: adopt transient reply markers --- build/api/index.js | 11 ++++++++++ build/cli/index.js | 11 ++++++++++ build/github_action/index.js | 14 ++++++++++++- ...tic-github-publication-and-notification.md | 5 ++++- .../publication_identity_policy.test.ts | 14 +++++++++++++ .../policies/publication_identity_policy.ts | 13 ++++++++++++ .../__tests__/publish_resume_use_case.test.ts | 4 ++-- .../reply_publication_workflow.test.ts | 21 +++++++++++++++++++ .../common/reply_publication_workflow.ts | 11 ++++++++-- 9 files changed, 98 insertions(+), 6 deletions(-) diff --git a/build/api/index.js b/build/api/index.js index 6f65de051..6b34fe4a1 100644 --- a/build/api/index.js +++ b/build/api/index.js @@ -1044,6 +1044,7 @@ exports.buildPublicationMarker = buildPublicationMarker; exports.parsePublicationMarker = parsePublicationMarker; exports.buildPublicationReplyMarker = buildPublicationReplyMarker; exports.parsePublicationReplyMarker = parsePublicationReplyMarker; +exports.readablePublicationReplyCorrelationIds = readablePublicationReplyCorrelationIds; exports.buildDuplicateMarker = buildDuplicateMarker; const node_crypto_1 = __nccwpck_require__(6005); const github_publication_1 = __nccwpck_require__(5793); @@ -1105,6 +1106,16 @@ function parsePublicationReplyMarker(body) { return undefined; return Object.freeze({ target: match[1], correlationId: match[2], messageKey: match[3], digest: match[4] }); } +/** + * Reads the stable issue-comment identity plus the short-lived namespaced form + * emitted during migration. Review-comment identities remain transport-scoped. + */ +function readablePublicationReplyCorrelationIds(correlationId) { + const issueComment = correlationId.match(/^comment:([1-9]\d*)$/u); + return Object.freeze(issueComment + ? [correlationId, `comment:issue_comment:${issueComment[1]}`] + : [correlationId]); +} function buildDuplicateMarker(canonicalCommentId) { if (!Number.isSafeInteger(canonicalCommentId) || canonicalCommentId < 1) { throw new Error('Canonical comment id must be a positive integer.'); diff --git a/build/cli/index.js b/build/cli/index.js index 3cb518a73..c3eb232b7 100755 --- a/build/cli/index.js +++ b/build/cli/index.js @@ -43738,6 +43738,7 @@ exports.buildPublicationMarker = buildPublicationMarker; exports.parsePublicationMarker = parsePublicationMarker; exports.buildPublicationReplyMarker = buildPublicationReplyMarker; exports.parsePublicationReplyMarker = parsePublicationReplyMarker; +exports.readablePublicationReplyCorrelationIds = readablePublicationReplyCorrelationIds; exports.buildDuplicateMarker = buildDuplicateMarker; const node_crypto_1 = __nccwpck_require__(6005); const github_publication_1 = __nccwpck_require__(35793); @@ -43799,6 +43800,16 @@ function parsePublicationReplyMarker(body) { return undefined; return Object.freeze({ target: match[1], correlationId: match[2], messageKey: match[3], digest: match[4] }); } +/** + * Reads the stable issue-comment identity plus the short-lived namespaced form + * emitted during migration. Review-comment identities remain transport-scoped. + */ +function readablePublicationReplyCorrelationIds(correlationId) { + const issueComment = correlationId.match(/^comment:([1-9]\d*)$/u); + return Object.freeze(issueComment + ? [correlationId, `comment:issue_comment:${issueComment[1]}`] + : [correlationId]); +} function buildDuplicateMarker(canonicalCommentId) { if (!Number.isSafeInteger(canonicalCommentId) || canonicalCommentId < 1) { throw new Error('Canonical comment id must be a positive integer.'); diff --git a/build/github_action/index.js b/build/github_action/index.js index 1553aded4..59eae0bf9 100644 --- a/build/github_action/index.js +++ b/build/github_action/index.js @@ -44661,6 +44661,7 @@ exports.buildPublicationMarker = buildPublicationMarker; exports.parsePublicationMarker = parsePublicationMarker; exports.buildPublicationReplyMarker = buildPublicationReplyMarker; exports.parsePublicationReplyMarker = parsePublicationReplyMarker; +exports.readablePublicationReplyCorrelationIds = readablePublicationReplyCorrelationIds; exports.buildDuplicateMarker = buildDuplicateMarker; const node_crypto_1 = __nccwpck_require__(6005); const github_publication_1 = __nccwpck_require__(35793); @@ -44722,6 +44723,16 @@ function parsePublicationReplyMarker(body) { return undefined; return Object.freeze({ target: match[1], correlationId: match[2], messageKey: match[3], digest: match[4] }); } +/** + * Reads the stable issue-comment identity plus the short-lived namespaced form + * emitted during migration. Review-comment identities remain transport-scoped. + */ +function readablePublicationReplyCorrelationIds(correlationId) { + const issueComment = correlationId.match(/^comment:([1-9]\d*)$/u); + return Object.freeze(issueComment + ? [correlationId, `comment:issue_comment:${issueComment[1]}`] + : [correlationId]); +} function buildDuplicateMarker(canonicalCommentId) { if (!Number.isSafeInteger(canonicalCommentId) || canonicalCommentId < 1) { throw new Error('Canonical comment id must be a positive integer.'); @@ -56844,10 +56855,11 @@ async function reconcileReply(context, comments) { } function matchingReplies(comments, context) { const expectedTarget = (0, github_publication_1.publicationTargetToken)(context.intent.target); + const readableCorrelations = new Set((0, publication_identity_policy_1.readablePublicationReplyCorrelationIds)(context.intent.correlationId)); return comments.filter(comment => { const marker = (0, publication_identity_policy_1.parsePublicationReplyMarker)(comment.body); return marker?.target === expectedTarget - && marker.correlationId === context.intent.correlationId + && readableCorrelations.has(marker.correlationId) && marker.messageKey === context.intent.messageKey && (0, github_user_policy_1.githubUsersMatch)(comment.user?.login ?? '', context.botLogin); }); diff --git a/specs/semantic-github-publication-and-notification.md b/specs/semantic-github-publication-and-notification.md index b4a75d539..cdd5b0be7 100644 --- a/specs/semantic-github-publication-and-notification.md +++ b/specs/semantic-github-publication-and-notification.md @@ -376,7 +376,10 @@ The shared marker format is: - Reply correlation preserves the established `comment:` identity for `issue_comment` replay compatibility. Review comments use the distinct `comment:pull_request_review_comment:` identity; - transports MUST NOT share a fallback correlation. + transports MUST NOT share a fallback correlation. Readers MUST also adopt the + transient `comment:issue_comment:` form emitted during + migration and compact it with the stable identity; new writes MUST NOT use + that transient form. - Commit-derived cards MUST revalidate the expected head SHA before update. - Revisioned operations MUST reject any revision lower than the stored revision. - Events without an orderable revision may update only after the shared workflow diff --git a/src/application/policies/__tests__/publication_identity_policy.test.ts b/src/application/policies/__tests__/publication_identity_policy.test.ts index 999381ba8..a9c38dc37 100644 --- a/src/application/policies/__tests__/publication_identity_policy.test.ts +++ b/src/application/policies/__tests__/publication_identity_policy.test.ts @@ -5,6 +5,7 @@ import { createSemanticDigest, parsePublicationMarker, parsePublicationReplyMarker, + readablePublicationReplyCorrelationIds, } from '../publication_identity_policy'; const marker = { @@ -80,4 +81,17 @@ describe('publication identity policy', () => { })).toThrow('invalid digest'); expect(parsePublicationReplyMarker(null)).toBeUndefined(); }); + + it('reads the transient issue-comment namespace without widening review correlations', () => { + expect(readablePublicationReplyCorrelationIds('comment:99')).toEqual([ + 'comment:99', + 'comment:issue_comment:99', + ]); + expect(readablePublicationReplyCorrelationIds( + 'comment:pull_request_review_comment:99', + )).toEqual(['comment:pull_request_review_comment:99']); + expect(readablePublicationReplyCorrelationIds('event:0123abcd')).toEqual([ + 'event:0123abcd', + ]); + }); }); diff --git a/src/application/policies/publication_identity_policy.ts b/src/application/policies/publication_identity_policy.ts index 852021d14..9d6a0c9c5 100644 --- a/src/application/policies/publication_identity_policy.ts +++ b/src/application/policies/publication_identity_policy.ts @@ -73,6 +73,19 @@ export function parsePublicationReplyMarker(body: string | null | undefined): Pu return Object.freeze({ target: match[1], correlationId: match[2], messageKey: match[3], digest: match[4] }); } +/** + * Reads the stable issue-comment identity plus the short-lived namespaced form + * emitted during migration. Review-comment identities remain transport-scoped. + */ +export function readablePublicationReplyCorrelationIds( + correlationId: string, +): readonly string[] { + const issueComment = correlationId.match(/^comment:([1-9]\d*)$/u); + return Object.freeze(issueComment + ? [correlationId, `comment:issue_comment:${issueComment[1]}`] + : [correlationId]); +} + export function buildDuplicateMarker(canonicalCommentId: number): string { if (!Number.isSafeInteger(canonicalCommentId) || canonicalCommentId < 1) { throw new Error('Canonical comment id must be a positive integer.'); diff --git a/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts b/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts index 3b47bc923..2ef9cca3a 100644 --- a/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts +++ b/src/application/usecases/steps/common/__tests__/publish_resume_use_case.test.ts @@ -98,11 +98,11 @@ describe('PublishResultUseCase semantic compatibility boundary', () => { expect(comments.values[0].body).not.toContain('legacy wrapper'); }); - it('recognizes a reply marker written before review-comment correlation was namespaced', async () => { + it('recognizes the transient namespaced issue-comment marker without creating a duplicate', async () => { const comments = inMemoryComments([{ id: 7, user: { login: 'vypbot' }, - body: '\n\nExisting response.', + body: '\n\nExisting response.', }]); const result = new Result({ id: 'Comment.Help', success: true, executed: true, diff --git a/src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts b/src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts index 090a93731..1c292af0c 100644 --- a/src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts +++ b/src/application/usecases/steps/common/__tests__/reply_publication_workflow.test.ts @@ -59,6 +59,27 @@ describe('reply publication workflow', () => { expect(repository.comments.find(comment => comment.id === 9)?.body).toContain('/issues/7#issuecomment-3'); }); + it('adopts and compacts the transient issue-comment correlation namespace', async () => { + const seeded = ports(); + await reconcileReply(context(), seeded); + const currentBody = seeded.comments[0].body as string; + const namespacedBody = currentBody.replace( + 'correlation="comment:41"', + 'correlation="comment:issue_comment:41"', + ); + const repository = ports([ + { id: 8, body: currentBody, user: { login: 'vypbot' } }, + { id: 3, body: namespacedBody, user: { login: 'vypbot' } }, + ]); + + await expect(reconcileReply(context(), repository)).resolves.toEqual({ + effect: 'unchanged', canonicalCommentId: 3, duplicatesCompacted: 1, + }); + expect(repository.addComment).not.toHaveBeenCalled(); + expect(repository.comments.find(comment => comment.id === 8)?.body) + .toContain('/issues/7#issuecomment-3'); + }); + it('does no provider work without a trusted bot identity', async () => { const repository = ports(); await expect(reconcileReply({ ...context(), botLogin: '' }, repository)).resolves.toEqual({ diff --git a/src/application/usecases/steps/common/reply_publication_workflow.ts b/src/application/usecases/steps/common/reply_publication_workflow.ts index 4001da733..cb4681596 100644 --- a/src/application/usecases/steps/common/reply_publication_workflow.ts +++ b/src/application/usecases/steps/common/reply_publication_workflow.ts @@ -1,7 +1,11 @@ import { publicationTargetToken } from '../../../../domain/github_publication'; import { githubUsersMatch } from '../../../../domain/github_user_policy'; import type { BoundIssueCommentPublicationPort, IssueCommentPublicationTarget } from '../../../ports/issue_lifecycle_ports'; -import { buildDuplicateMarker, parsePublicationReplyMarker } from '../../../policies/publication_identity_policy'; +import { + buildDuplicateMarker, + parsePublicationReplyMarker, + readablePublicationReplyCorrelationIds, +} from '../../../policies/publication_identity_policy'; import { resolveStaticPublicationCatalog } from '../../../policies/publication_message_catalog'; import { renderSemanticReply, type SemanticReplyIntent } from '../../../policies/semantic_result_publication_policy'; @@ -46,10 +50,13 @@ function matchingReplies( context: ReplyPublicationContext, ): IssueCommentPublicationTarget[] { const expectedTarget = publicationTargetToken(context.intent.target); + const readableCorrelations = new Set( + readablePublicationReplyCorrelationIds(context.intent.correlationId), + ); return comments.filter(comment => { const marker = parsePublicationReplyMarker(comment.body); return marker?.target === expectedTarget - && marker.correlationId === context.intent.correlationId + && readableCorrelations.has(marker.correlationId) && marker.messageKey === context.intent.messageKey && githubUsersMatch(comment.user?.login ?? '', context.botLogin); });