@planu/cli 4.11.1 → 4.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/config/elicitation-questions.json +5 -17
- package/dist/engine/cascade-hooks/registry.js +0 -2
- package/dist/engine/challenge-scenarios-loader.d.ts +3 -3
- package/dist/engine/compliance-injector.d.ts +3 -3
- package/dist/engine/dashboard/kanban-logic.d.ts +1 -1
- package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts +1 -1
- package/dist/engine/doc-generator/proposal/proposal-generator.d.ts +1 -1
- package/dist/engine/elicitation/answer-extractor.d.ts +1 -4
- package/dist/engine/elicitation/answer-extractor.js +197 -68
- package/dist/engine/elicitation/decision-gap-detector.js +59 -79
- package/dist/engine/elicitation/index.d.ts +0 -1
- package/dist/engine/elicitation/index.js +0 -1
- package/dist/engine/elicitation/question-generator.js +10 -3
- package/dist/engine/elicitation/question-grounding-gate.js +83 -34
- package/dist/engine/evidence-gates/lifecycle-gate.js +27 -14
- package/dist/engine/evidence-index/index-builder.js +7 -2
- package/dist/engine/frontmatter-sha/frozen-fields.d.ts +1 -1
- package/dist/engine/handoff-artifacts/schemas/red-tests-handoff.d.ts +2 -2
- package/dist/engine/handoff-artifacts/schemas.d.ts +22 -22
- package/dist/engine/human-summary.js +3 -3
- package/dist/engine/implementation-contract/common.d.ts +1 -1
- package/dist/engine/implementation-contract/renderer.js +17 -39
- package/dist/engine/keyword-matcher/stopwords.d.ts +2 -2
- package/dist/engine/lifecycle-hints.d.ts +1 -1
- package/dist/engine/lifecycle-hints.js +13 -29
- package/dist/engine/provider-adapters/adapters/claude.d.ts +1 -1
- package/dist/engine/provider-adapters/adapters/gemini.d.ts +1 -1
- package/dist/engine/provider-adapters/adapters/gpt4.d.ts +1 -1
- package/dist/engine/provider-adapters/adapters/markdown.d.ts +1 -1
- package/dist/engine/safety/atomic-write-file.d.ts +1 -1
- package/dist/engine/safety/cross-process-lock.d.ts +1 -1
- package/dist/engine/safety/path-sanitizer.d.ts +1 -1
- package/dist/engine/security-analyzer/profile-detector.js +48 -16
- package/dist/engine/spec-format/copula-blacklist.d.ts +2 -2
- package/dist/engine/spec-format/lean-spec-generator.d.ts +2 -2
- package/dist/engine/spec-format/lean-spec-generator.js +9 -10
- package/dist/engine/spec-format/metadata-value-policy.d.ts +20 -20
- package/dist/engine/spec-generator/fallback-generator.js +13 -20
- package/dist/engine/spec-grounding/contract.d.ts +5 -0
- package/dist/engine/spec-grounding/contract.js +10 -6
- package/dist/engine/spec-migrator/lean-migration.js +9 -16
- package/dist/engine/text-signal-boundaries.js +45 -12
- package/dist/engine/universal-rules/rules/planu-modes.js +0 -2
- package/dist/engine/web-fetcher/stack-advisor.d.ts +2 -2
- package/dist/engine/web-fetcher/stack-advisor.js +78 -17
- package/dist/engine/well-known/manifest-schema.d.ts +33 -33
- package/dist/engine/workers/schema.d.ts +3 -3
- package/dist/hosts/claude-code/ux/mcp-resources.d.ts +4 -4
- package/dist/hosts/codex/rest-shim.d.ts +1 -1
- package/dist/native/lightweight-command-catalog.d.ts +30 -30
- package/dist/server/openapi-spec.d.ts +132 -132
- package/dist/tools/challenge-spec/challenge-report.d.ts +13 -0
- package/dist/tools/challenge-spec/challenge-report.js +55 -0
- package/dist/tools/challenge-spec/event-challenge-scenarios.js +8 -27
- package/dist/tools/challenge-spec/resilience-challenge-scenarios-a.js +16 -32
- package/dist/tools/challenge-spec/resilience-challenge-scenarios-b.js +15 -20
- package/dist/tools/challenge-spec/scenario-collector.d.ts +13 -0
- package/dist/tools/challenge-spec/scenario-collector.js +86 -0
- package/dist/tools/challenge-spec/scenarios-data.js +10 -9
- package/dist/tools/challenge-spec/scenarios-failure.js +38 -54
- package/dist/tools/challenge-spec/scenarios-scale.js +14 -15
- package/dist/tools/challenge-spec/scenarios-security.d.ts +1 -1
- package/dist/tools/challenge-spec/scenarios-security.js +18 -11
- package/dist/tools/challenge-spec/scenarios-utils.d.ts +31 -5
- package/dist/tools/challenge-spec/scenarios-utils.js +102 -6
- package/dist/tools/challenge-spec/security-challenge-scenarios.d.ts +1 -1
- package/dist/tools/challenge-spec/security-challenge-scenarios.js +20 -25
- package/dist/tools/challenge-spec.js +46 -124
- package/dist/tools/clarify-requirements/multiple-choice.js +1 -1
- package/dist/tools/clarify-requirements/questions-context.js +11 -11
- package/dist/tools/clarify-requirements/questions.js +3 -23
- package/dist/tools/clarify-requirements.js +38 -0
- package/dist/tools/compliance-gate-handler.d.ts +1 -1
- package/dist/tools/create-spec/autopilot-analyzer.d.ts +1 -1
- package/dist/tools/create-spec/autopilot-analyzer.js +29 -54
- package/dist/tools/create-spec/post-creation.js +23 -4
- package/dist/tools/create-spec.js +51 -168
- package/dist/tools/delete-first/suggest-deletions.d.ts +1 -1
- package/dist/tools/elicit-requirements-handler.js +20 -12
- package/dist/tools/generate-batch-script.d.ts +2 -2
- package/dist/tools/init-project/rules-generator.js +0 -1
- package/dist/tools/oauth-handler.d.ts +1 -1
- package/dist/tools/register-sdd-tools.d.ts +1 -1
- package/dist/tools/register-spec-tools/core-spec-tools.js +11 -2
- package/dist/tools/render-spec-for-provider.d.ts +2 -2
- package/dist/tools/scan-orphan-spec-refs.d.ts +6 -6
- package/dist/tools/schemas/agents.d.ts +7 -7
- package/dist/tools/schemas/analysis.d.ts +11 -11
- package/dist/tools/schemas/delete.d.ts +5 -5
- package/dist/tools/schemas/github.d.ts +4 -4
- package/dist/tools/schemas/ide-config.d.ts +12 -12
- package/dist/tools/schemas/infra.d.ts +9 -9
- package/dist/tools/schemas/lifecycle.d.ts +50 -50
- package/dist/tools/schemas/llm-provider-schemas.d.ts +7 -7
- package/dist/tools/schemas/plugins-schemas.d.ts +12 -12
- package/dist/tools/schemas/project-graph.d.ts +6 -6
- package/dist/tools/schemas/rbac.d.ts +2 -2
- package/dist/tools/schemas/registry.d.ts +3 -3
- package/dist/tools/schemas/runtime-security.d.ts +2 -2
- package/dist/tools/schemas/session.d.ts +2 -2
- package/dist/tools/schemas/spec.d.ts +15 -15
- package/dist/tools/schemas/token-intelligence.d.ts +6 -6
- package/dist/tools/schemas/token-optimization.d.ts +2 -2
- package/dist/tools/schemas/workers-schema.d.ts +4 -4
- package/dist/tools/skill-bootstrap-handler.d.ts +7 -7
- package/dist/tools/suggest-stack.js +49 -13
- package/dist/tools/tool-registry/deprecated-stubs.d.ts +1 -1
- package/dist/tools/triage-request.d.ts +3 -3
- package/dist/tools/update-status/dod-gates.d.ts +8 -0
- package/dist/tools/update-status/dod-gates.js +68 -43
- package/dist/tools/update-status/file-sync.d.ts +1 -1
- package/dist/tools/update-status/file-sync.js +10 -45
- package/dist/tools/update-status/index.js +133 -97
- package/dist/tools/update-status/qa-gate.js +26 -0
- package/dist/tools/update-status/response-builder.d.ts +1 -1
- package/dist/tools/update-status/response-builder.js +11 -19
- package/dist/tools/update-status/transition-guard.d.ts +2 -2
- package/dist/tools/update-status/transition-guard.js +11 -8
- package/dist/tools/update-status-actions.d.ts +0 -1
- package/dist/tools/update-status-actions.js +10 -26
- package/dist/tools/update-status-convention-gate.d.ts +1 -6
- package/dist/tools/update-status-convention-gate.js +9 -33
- package/dist/tools/validate-lint.d.ts +1 -1
- package/dist/tools/validate-lint.js +39 -1
- package/dist/types/docs.d.ts +0 -4
- package/dist/types/env.d.ts +0 -4
- package/dist/types/estimation.d.ts +0 -2
- package/dist/types/multi-teammate-review.d.ts +4 -4
- package/dist/types/plugin-install.d.ts +7 -7
- package/dist/types/spec/core.d.ts +20 -0
- package/dist/types/spec/inputs.d.ts +1 -1
- package/dist/types/stack/index.d.ts +1 -1
- package/dist/types/stack/recommend.d.ts +10 -0
- package/package.json +21 -18
- package/planu-native.json +1 -1
- package/planu-plugin.json +1 -1
- package/src/i18n/messages/en.json +0 -72
- package/src/i18n/messages/es.json +0 -72
- package/src/i18n/messages/pt.json +0 -72
- package/dist/config/elicitation-dimensions.json +0 -51
- package/dist/engine/actuals-estimator.d.ts +0 -14
- package/dist/engine/actuals-estimator.js +0 -64
- package/dist/engine/cascade-hooks/hooks/living-specs.hook.d.ts +0 -3
- package/dist/engine/cascade-hooks/hooks/living-specs.hook.js +0 -19
- package/dist/engine/elicitation/non-interactive-defaults.d.ts +0 -9
- package/dist/engine/elicitation/non-interactive-defaults.js +0 -28
- package/dist/engine/elicitation/option-builder.d.ts +0 -9
- package/dist/engine/elicitation/option-builder.js +0 -85
- package/dist/engine/progress-writer.d.ts +0 -10
- package/dist/engine/progress-writer.js +0 -63
- package/dist/tools/clarify-requirements/interview-mode.d.ts +0 -42
- package/dist/tools/clarify-requirements/interview-mode.js +0 -342
- package/dist/tools/update-status/mode-hints.d.ts +0 -4
- package/dist/tools/update-status/mode-hints.js +0 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## [4.11.3] - 2026-07-10
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
- fix(release): classify npm 404 visibility correctly
|
|
5
|
+
- fix(planu): harden release and grounded spec flow
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## [4.11.2] - 2026-07-10
|
|
9
|
+
|
|
10
|
+
### Bug Fixes
|
|
11
|
+
- fix(specs): ground lifecycle output and adopt TypeScript 7
|
|
12
|
+
|
|
13
|
+
### Chores
|
|
14
|
+
- chore(deps): update patch dependencies
|
|
15
|
+
|
|
16
|
+
|
|
1
17
|
## [4.11.1] - 2026-07-09
|
|
2
18
|
|
|
3
19
|
### Bug Fixes
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
{
|
|
3
3
|
"id": "users",
|
|
4
4
|
"category": "users",
|
|
5
|
-
"questionTemplate": "
|
|
6
|
-
"hint": "e.g., \"authenticated users\", \"admin only\", \"all visitors\"",
|
|
5
|
+
"questionTemplate": "Which actors, systems, or processes are explicitly in scope for \"{title}\"?",
|
|
7
6
|
"required": true,
|
|
8
7
|
"answerType": "text",
|
|
9
8
|
"condition": "always"
|
|
@@ -11,8 +10,7 @@
|
|
|
11
10
|
{
|
|
12
11
|
"id": "success",
|
|
13
12
|
"category": "success",
|
|
14
|
-
"questionTemplate": "
|
|
15
|
-
"hint": "Describe the outcome from the user's perspective",
|
|
13
|
+
"questionTemplate": "Which explicit observations or checks will prove that \"{title}\" is complete?",
|
|
16
14
|
"required": true,
|
|
17
15
|
"answerType": "text",
|
|
18
16
|
"condition": "always"
|
|
@@ -34,8 +32,7 @@
|
|
|
34
32
|
{
|
|
35
33
|
"id": "edge_cases",
|
|
36
34
|
"category": "behavior",
|
|
37
|
-
"questionTemplate": "
|
|
38
|
-
"hint": "e.g., \"show error message\", \"redirect to login\", \"fail silently\"",
|
|
35
|
+
"questionTemplate": "Which failure conditions are explicitly in scope?",
|
|
39
36
|
"required": false,
|
|
40
37
|
"answerType": "text",
|
|
41
38
|
"condition": "always"
|
|
@@ -43,8 +40,7 @@
|
|
|
43
40
|
{
|
|
44
41
|
"id": "data",
|
|
45
42
|
"category": "data",
|
|
46
|
-
"questionTemplate": "
|
|
47
|
-
"hint": "List the key fields/entities involved",
|
|
43
|
+
"questionTemplate": "Does this request require a confirmed data change? If yes, describe only the known data boundary.",
|
|
48
44
|
"required": false,
|
|
49
45
|
"answerType": "text",
|
|
50
46
|
"condition": "data",
|
|
@@ -53,18 +49,10 @@
|
|
|
53
49
|
{
|
|
54
50
|
"id": "api_contract",
|
|
55
51
|
"category": "constraints",
|
|
56
|
-
"questionTemplate": "Does this require
|
|
52
|
+
"questionTemplate": "Does this request explicitly require an API contract change?",
|
|
57
53
|
"required": false,
|
|
58
54
|
"answerType": "yes-no",
|
|
59
55
|
"condition": "api",
|
|
60
56
|
"conditionKeywords": ["api", "endpoint"]
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"id": "performance",
|
|
64
|
-
"category": "constraints",
|
|
65
|
-
"questionTemplate": "Are there performance requirements (response time, load, etc.)?",
|
|
66
|
-
"required": false,
|
|
67
|
-
"answerType": "text",
|
|
68
|
-
"condition": "always"
|
|
69
57
|
}
|
|
70
58
|
]
|
|
@@ -9,7 +9,6 @@ import { gitAutoStageHook } from './hooks/git-auto-stage.hook.js';
|
|
|
9
9
|
import { velocityProfileHook } from './hooks/velocity-profile.hook.js';
|
|
10
10
|
import { estimateCalibrationHook } from './hooks/estimate-calibration.hook.js';
|
|
11
11
|
import { lessonDriftHook } from './hooks/lesson-drift.hook.js';
|
|
12
|
-
import { livingSpecsHook } from './hooks/living-specs.hook.js';
|
|
13
12
|
import { nextSpecResolverHook } from './hooks/next-spec-resolver.hook.js';
|
|
14
13
|
import { orchestrationPlanHook } from './hooks/orchestration-plan.hook.js';
|
|
15
14
|
import { completeLoopHook } from './hooks/complete-loop.hook.js';
|
|
@@ -38,7 +37,6 @@ export const HOOKS = Object.freeze([
|
|
|
38
37
|
velocityProfileHook,
|
|
39
38
|
estimateCalibrationHook,
|
|
40
39
|
lessonDriftHook,
|
|
41
|
-
livingSpecsHook,
|
|
42
40
|
nextSpecResolverHook,
|
|
43
41
|
orchestrationPlanHook,
|
|
44
42
|
completeLoopHook,
|
|
@@ -6,15 +6,15 @@ export declare const ChallengeScenarioPatternSchema: z.ZodObject<{
|
|
|
6
6
|
triggerKeywords: z.ZodArray<z.ZodString>;
|
|
7
7
|
scenario: z.ZodString;
|
|
8
8
|
probability: z.ZodEnum<{
|
|
9
|
+
high: "high";
|
|
9
10
|
low: "low";
|
|
10
11
|
medium: "medium";
|
|
11
|
-
high: "high";
|
|
12
12
|
}>;
|
|
13
13
|
impact: z.ZodEnum<{
|
|
14
|
+
critical: "critical";
|
|
15
|
+
high: "high";
|
|
14
16
|
low: "low";
|
|
15
17
|
medium: "medium";
|
|
16
|
-
high: "high";
|
|
17
|
-
critical: "critical";
|
|
18
18
|
}>;
|
|
19
19
|
handlingKeywords: z.ZodArray<z.ZodString>;
|
|
20
20
|
handlingFound: z.ZodString;
|
|
@@ -5,11 +5,11 @@ export declare const ComplianceFrameworkDefSchema: z.ZodObject<{
|
|
|
5
5
|
name: z.ZodString;
|
|
6
6
|
fullName: z.ZodString;
|
|
7
7
|
type: z.ZodEnum<{
|
|
8
|
-
security: "security";
|
|
9
|
-
testing: "testing";
|
|
10
|
-
quality: "quality";
|
|
11
8
|
accessibility: "accessibility";
|
|
12
9
|
privacy: "privacy";
|
|
10
|
+
quality: "quality";
|
|
11
|
+
security: "security";
|
|
12
|
+
testing: "testing";
|
|
13
13
|
}>;
|
|
14
14
|
regions: z.ZodArray<z.ZodString>;
|
|
15
15
|
triggerKeywords: z.ZodArray<z.ZodString>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DashboardMetrics, DashboardSpecCard, ProjectHealthScore, SpecStatusUpdateResult, StatusTransitionMap } from '../../types/index.js';
|
|
2
2
|
/** Ordered list of valid spec statuses */
|
|
3
|
-
export declare const VALID_STATUSES: readonly [
|
|
3
|
+
export declare const VALID_STATUSES: readonly ['draft', 'review', 'approved', 'implementing', 'done', 'discarded'];
|
|
4
4
|
/** Valid forward + one-step-back transitions */
|
|
5
5
|
export declare const STATUS_TRANSITIONS: StatusTransitionMap;
|
|
6
6
|
/** Check if a status string is a valid spec status */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SpecRisk, RiskCategory } from '../../../types/risk.js';
|
|
2
|
-
declare const IMPACT_LEVELS: readonly [
|
|
2
|
+
declare const IMPACT_LEVELS: readonly ['low', 'medium', 'high', 'very_high', 'critical'];
|
|
3
3
|
declare const PROB_LEVELS: readonly [20, 40, 60, 80, 100];
|
|
4
4
|
declare const CATEGORY_COLORS: Record<RiskCategory, string>;
|
|
5
5
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ProposalData } from '../../../types/docs.js';
|
|
2
2
|
/** Canonical section IDs for includeSections / excludeSections filtering. */
|
|
3
|
-
export declare const SECTION_IDS: readonly [
|
|
3
|
+
export declare const SECTION_IDS: readonly ['hero', 'executive-summary', 'scope', 'spec-breakdown', 'timeline', 'budget', 'investment-summary', 'architecture', 'risk', 'decisions', 'impact-analysis', 'gap-analysis', 'team-resources', 'pricing-methodology', 'design-system', 'quality-metrics', 'living-spec', 'assumptions', 'alternatives', 'post-launch', 'module-mapping'];
|
|
4
4
|
/**
|
|
5
5
|
* Generate a self-contained HTML proposal document.
|
|
6
6
|
* All sections are conditional — empty string returned when data is insufficient.
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import type { DescriptionSignals } from '../../types/index.js';
|
|
2
|
-
/**
|
|
3
|
-
* Scan a feature description for pre-existing answers.
|
|
4
|
-
* Any signal set to true means the corresponding question can be suppressed.
|
|
5
|
-
*/
|
|
2
|
+
/** Scan a feature description for explicit, affirmed request evidence. */
|
|
6
3
|
export declare function extractSignals(description: string): DescriptionSignals;
|
|
7
4
|
//# sourceMappingURL=answer-extractor.d.ts.map
|
|
@@ -1,96 +1,225 @@
|
|
|
1
1
|
// src/engine/elicitation/answer-extractor.ts
|
|
2
|
-
// SPEC-624
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const DESTRUCTIVE_TERMS = /\b(delete|remove|archive|discard|drop|truncate|revoke|reset)\b/;
|
|
13
|
-
const RISK_TERMS = /\b(approval|approve|payment|billing|auth|login|permission|role|delete|remove|migration|sync|webhook|external|api|rls|privacy|security)\b/gi;
|
|
14
|
-
const PAYMENT_PROVIDERS = [
|
|
15
|
-
{ name: 'Stripe', pattern: /\bstripe\b/ },
|
|
16
|
-
{ name: 'PayPal', pattern: /\bpaypal\b/ },
|
|
17
|
-
{ name: 'Paddle', pattern: /\bpaddle\b/ },
|
|
18
|
-
{ name: 'Braintree', pattern: /\bbraintree\b/ },
|
|
19
|
-
{ name: 'Square', pattern: /\bsquare\b/ },
|
|
20
|
-
{ name: 'Adyen', pattern: /\badyen\b/ },
|
|
2
|
+
// SPEC-624 / SPEC-1121: Extracts affirmed request evidence for contextual questions.
|
|
3
|
+
const UI_TARGET_PATTERN = /\b(component|ui|page|css|style|layout|form|button|modal|dialog)\b/i;
|
|
4
|
+
const TARGET_TERMS = [
|
|
5
|
+
['frontend', UI_TARGET_PATTERN],
|
|
6
|
+
['backend', /\b(endpoint|route|handler|middleware|controller|server action)\b/i],
|
|
7
|
+
[
|
|
8
|
+
'tooling',
|
|
9
|
+
/\b(compiler|typecheck|toolchain|typescript|build script|package alias|dependency|release guard)\b/i,
|
|
10
|
+
],
|
|
11
|
+
['infrastructure', /\b(deploy|ci|cd|docker|pipeline|configuration|environment)\b/i],
|
|
21
12
|
];
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
const BILLING_TERMS = [
|
|
14
|
+
['payment', /\bpayments?\b/i],
|
|
15
|
+
['billing', /\bbilling\b/i],
|
|
16
|
+
['invoice', /\binvoices?\b/i],
|
|
17
|
+
['subscription', /\bsubscriptions?\b/i],
|
|
18
|
+
['checkout', /\bcheckout\b/i],
|
|
19
|
+
['pricing', /\bpricing\b/i],
|
|
20
|
+
];
|
|
21
|
+
const DATABASE_TERMS = [
|
|
22
|
+
['database', /\bdatabases?\b/i],
|
|
23
|
+
['schema', /\bschemas?\b/i],
|
|
24
|
+
['table', /\btables?\b/i],
|
|
25
|
+
['sql', /\bsql\b/i],
|
|
26
|
+
['rls', /\brls\b/i],
|
|
27
|
+
['persistence', /\b(?:persist|persists|persisted|persistence|data access)\b/i],
|
|
28
|
+
['data-migration', /\b(?:database|schema|table)\s+migrations?\b|\bmigrate\s+(?:a\s+)?table\b/i],
|
|
29
|
+
];
|
|
30
|
+
const ACTOR_TERMS = [
|
|
31
|
+
['admin', /\b(?:admin|administrator)\b/i],
|
|
32
|
+
['manager', /\bmanager\b/i],
|
|
33
|
+
['owner', /\bowner\b/i],
|
|
34
|
+
['customer', /\bcustomer\b/i],
|
|
35
|
+
['user', /\buser\b/i],
|
|
36
|
+
['member', /\bmember\b/i],
|
|
37
|
+
['team', /\bteam\b/i],
|
|
38
|
+
['approver', /\bapprover\b/i],
|
|
39
|
+
];
|
|
40
|
+
const DATA_OBJECT_TERMS = [
|
|
41
|
+
['account', /\baccounts?\b/i],
|
|
42
|
+
['profile', /\bprofiles?\b/i],
|
|
43
|
+
['request', /\brequests?\b/i],
|
|
44
|
+
['invoice', /\binvoices?\b/i],
|
|
45
|
+
['subscription', /\bsubscriptions?\b/i],
|
|
46
|
+
['payment', /\bpayments?\b/i],
|
|
47
|
+
['order', /\borders?\b/i],
|
|
48
|
+
['report', /\breports?\b/i],
|
|
49
|
+
['file', /\bfiles?\b/i],
|
|
50
|
+
['avatar', /\bavatars?\b/i],
|
|
51
|
+
['table', /\btables?\b/i],
|
|
52
|
+
['policy', /\bpolic(?:y|ies)\b/i],
|
|
53
|
+
['record', /\brecords?\b/i],
|
|
54
|
+
['settings', /\bsettings\b/i],
|
|
55
|
+
];
|
|
56
|
+
const PERMISSION_TERMS = [
|
|
57
|
+
['approval', /\b(?:approval|approve|approves|reject|rejects)\b/i],
|
|
58
|
+
['authorization', /\b(?:authorization|permission|role|rbac|access control)\b/i],
|
|
59
|
+
['authentication', /\b(?:auth|authentication|login|oauth)\b/i],
|
|
60
|
+
['ownership', /\b(?:owner|allowed to|who can)\b/i],
|
|
61
|
+
];
|
|
62
|
+
const DESTRUCTIVE_TERMS = [
|
|
63
|
+
['delete', /\b(?:delete|remove|archive|discard|drop|truncate|revoke|reset)\b/i],
|
|
64
|
+
];
|
|
65
|
+
const RISK_TERMS = [
|
|
66
|
+
...BILLING_TERMS,
|
|
67
|
+
...PERMISSION_TERMS,
|
|
68
|
+
['delete', /\b(?:delete|remove|archive|discard|drop|truncate|revoke|reset)\b/i],
|
|
69
|
+
['sync', /\bsync(?:hronize)?\b/i],
|
|
70
|
+
['webhook', /\bwebhooks?\b/i],
|
|
71
|
+
['external', /\b(?:external|third-party|remote)\b/i],
|
|
72
|
+
['privacy', /\bprivacy\b/i],
|
|
73
|
+
['security', /\bsecurity\b/i],
|
|
74
|
+
];
|
|
75
|
+
const ACTION_RE = /\b(add|adopt|build|create|fix|update|upgrade|replace|remove|delete|integrate|sync|import|export|approve|reject|upload|download|migrate|configure|validate|verify|retain|preserve)\b\s+(?:(?:a|an|the|new)\s+)?([a-z0-9][a-z0-9 -]{1,60})/i;
|
|
76
|
+
const NEGATION_RE = /\b(?:no|not|never|without|exclude|excludes|excluding|neither|nor|do not|does not|don't|doesn't|must not|should not|isn't|aren't|non-goal)\b|\bout[- ]of[- ]scope\b/i;
|
|
77
|
+
const EXAMPLE_RE = /\b(?:example|examples|for example|e\.g\.|such as)\b/i;
|
|
78
|
+
const TEST_PROSE_RE = /\b(?:test|tests|tested|testing|fixture|fixtures|assert|assertion|coverage|regression)\b/i;
|
|
79
|
+
const INTERNAL_API_CONTEXT_RE = /\b(?:compiler|programmatic|package|library|ast|type(?:script)?|declaration)\s+api\b|\bapi\s+(?:compatibility|surface)\b/i;
|
|
80
|
+
const GENERIC_CANDIDATES = new Set([
|
|
81
|
+
'api',
|
|
82
|
+
'billing',
|
|
83
|
+
'checkout',
|
|
84
|
+
'integration',
|
|
85
|
+
'payment',
|
|
86
|
+
'provider',
|
|
87
|
+
'sdk',
|
|
88
|
+
'system',
|
|
89
|
+
]);
|
|
90
|
+
/** Scan a feature description for explicit, affirmed request evidence. */
|
|
26
91
|
export function extractSignals(description) {
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
// hasBilling: billing/payment keywords OR a specific provider was named
|
|
39
|
-
const hasBilling = BILLING_TERMS.test(lower) || namedProvider !== null;
|
|
40
|
-
const wordCount = description.trim().split(/\s+/).length;
|
|
41
|
-
const hasScope = wordCount >= 10;
|
|
92
|
+
const cleaned = stripIncidentalReferences(description);
|
|
93
|
+
const segments = splitSegments(cleaned);
|
|
94
|
+
const targetTerms = collectAffirmedTerms(segments, TARGET_TERMS);
|
|
95
|
+
const billingTerms = collectAffirmedTerms(segments, BILLING_TERMS);
|
|
96
|
+
const databaseTerms = collectAffirmedTerms(segments, DATABASE_TERMS);
|
|
97
|
+
const permissionTerms = collectAffirmedTerms(segments, PERMISSION_TERMS);
|
|
98
|
+
const integrations = extractExternalIntegrations(segments);
|
|
99
|
+
const namedProvider = extractNamedBillingCandidate(segments);
|
|
100
|
+
const lower = cleaned.toLowerCase();
|
|
42
101
|
const actionMatch = ACTION_RE.exec(lower);
|
|
43
102
|
const action = actionMatch?.[1] ?? null;
|
|
44
103
|
const domainObject = cleanDomainObject(actionMatch?.[2] ?? null);
|
|
45
|
-
const
|
|
46
|
-
const dataObjects = uniqueMatches(lower, DATA_OBJECT_TERMS);
|
|
47
|
-
const riskTerms = uniqueMatches(lower, RISK_TERMS);
|
|
48
|
-
const integrations = [
|
|
49
|
-
...PAYMENT_PROVIDERS.filter(({ pattern }) => pattern.test(lower)).map(({ name }) => name),
|
|
50
|
-
...extractExternalIntegrations(lower),
|
|
51
|
-
];
|
|
52
|
-
const hasPermissionRisk = PERMISSION_TERMS.test(lower);
|
|
53
|
-
const hasDestructiveAction = DESTRUCTIVE_TERMS.test(lower);
|
|
104
|
+
const wordCount = description.trim().split(/\s+/).filter(Boolean).length;
|
|
54
105
|
return {
|
|
55
|
-
hasTarget,
|
|
56
|
-
hasBilling,
|
|
57
|
-
hasDatabase,
|
|
58
|
-
hasUi,
|
|
106
|
+
hasTarget: targetTerms.length > 0,
|
|
107
|
+
hasBilling: billingTerms.length > 0 || namedProvider !== null,
|
|
108
|
+
hasDatabase: databaseTerms.length > 0,
|
|
109
|
+
hasUi: segments.some((segment) => hasAffirmedMatch(segment, UI_TARGET_PATTERN)),
|
|
59
110
|
namedProvider,
|
|
60
|
-
hasScope,
|
|
111
|
+
hasScope: wordCount >= 10,
|
|
61
112
|
action,
|
|
62
113
|
domainObject,
|
|
63
|
-
actors,
|
|
114
|
+
actors: collectAffirmedTerms(segments, ACTOR_TERMS),
|
|
64
115
|
integrations,
|
|
65
|
-
dataObjects,
|
|
66
|
-
hasPermissionRisk,
|
|
67
|
-
hasDestructiveAction,
|
|
68
|
-
riskTerms,
|
|
116
|
+
dataObjects: collectAffirmedTerms(segments, DATA_OBJECT_TERMS),
|
|
117
|
+
hasPermissionRisk: permissionTerms.length > 0,
|
|
118
|
+
hasDestructiveAction: collectAffirmedTerms(segments, DESTRUCTIVE_TERMS).length > 0,
|
|
119
|
+
riskTerms: collectAffirmedTerms(segments, RISK_TERMS),
|
|
69
120
|
};
|
|
70
121
|
}
|
|
71
122
|
function stripIncidentalReferences(description) {
|
|
72
123
|
return description
|
|
73
|
-
.replace(
|
|
74
|
-
.replace(
|
|
75
|
-
.replace(/\
|
|
124
|
+
.replace(/```[\s\S]*?```/g, ' ')
|
|
125
|
+
.replace(/`[^`]+`/g, ' ')
|
|
126
|
+
.replace(/\[[^\]]*\]\([^)]*\)/g, ' ')
|
|
127
|
+
.replace(/https?:\/\/\S+/gi, ' ')
|
|
128
|
+
.replace(/\B@[\w.-]+\/[\w.-]+(?:@[\w.-]+)?/g, ' ')
|
|
129
|
+
.replace(/\b[\w.-]+\/[\w./-]+\b/g, ' ')
|
|
130
|
+
.replace(/\b[\w-]+\.(?:ts|tsx|js|jsx|mjs|cjs|json|md|css|scss|py|go|rs|java|rb|php)\b/gi, ' ')
|
|
131
|
+
.replace(/\b[a-z0-9]+(?:-[a-z0-9]+)+(?:@[^\s,;]+)?\b/gi, ' ');
|
|
132
|
+
}
|
|
133
|
+
function splitSegments(description) {
|
|
134
|
+
return description
|
|
135
|
+
.split(/(?:\r?\n|[.;!?]+)\s*/)
|
|
136
|
+
.map((segment) => segment.replace(/^\s*(?:[-*]|\d+[.)])\s*/, '').trim())
|
|
137
|
+
.filter(Boolean);
|
|
138
|
+
}
|
|
139
|
+
function collectAffirmedTerms(segments, definitions) {
|
|
140
|
+
const found = [];
|
|
141
|
+
for (const [name, pattern] of definitions) {
|
|
142
|
+
if (segments.some((segment) => hasAffirmedMatch(segment, pattern))) {
|
|
143
|
+
found.push(name);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return found;
|
|
147
|
+
}
|
|
148
|
+
function hasAffirmedMatch(segment, pattern) {
|
|
149
|
+
const flags = pattern.flags.replace('g', '');
|
|
150
|
+
const match = new RegExp(pattern.source, flags).exec(segment);
|
|
151
|
+
if (!match) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
const prefix = segment.slice(0, match.index);
|
|
155
|
+
if (NEGATION_RE.test(prefix) || EXAMPLE_RE.test(prefix) || TEST_PROSE_RE.test(prefix)) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const matchedText = match[0].toLowerCase();
|
|
159
|
+
if (matchedText === 'api' && INTERNAL_API_CONTEXT_RE.test(segment)) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
76
163
|
}
|
|
77
164
|
function cleanDomainObject(value) {
|
|
78
165
|
if (value === null) {
|
|
79
166
|
return null;
|
|
80
167
|
}
|
|
81
168
|
const cleaned = value
|
|
82
|
-
.replace(/\b(that|with|to|from|when|where|and|or|but|using|in)\b.*$/i, '')
|
|
169
|
+
.replace(/\b(that|with|to|from|when|where|and|or|but|using|in|while)\b.*$/i, '')
|
|
83
170
|
.replace(/\s+/g, ' ')
|
|
84
171
|
.trim();
|
|
85
172
|
return cleaned.length > 0 ? cleaned : null;
|
|
86
173
|
}
|
|
87
|
-
function
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
174
|
+
function extractNamedBillingCandidate(segments) {
|
|
175
|
+
for (const segment of segments) {
|
|
176
|
+
if (!BILLING_TERMS.some(([, pattern]) => hasAffirmedMatch(segment, pattern))) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const candidate = extractCandidate(segment, [
|
|
180
|
+
/\b(?:integrate|use|using|via|with)\s+(?:with\s+)?([a-z][a-z0-9-]{1,40})\b/i,
|
|
181
|
+
/\bprovider\s*(?:is|:)\s*([a-z][a-z0-9-]{1,40})\b/i,
|
|
182
|
+
/\b([a-z][a-z0-9-]{1,40})\s+(?:payment|billing|checkout)\b/i,
|
|
183
|
+
/\b([a-z][a-z0-9-]{1,40})\s+(?:payment|billing|checkout)\s+(?:api|sdk|integration|provider)\b/i,
|
|
184
|
+
]);
|
|
185
|
+
if (candidate !== null) {
|
|
186
|
+
return normalizeCandidate(candidate);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
function extractExternalIntegrations(segments) {
|
|
192
|
+
const integrations = [];
|
|
193
|
+
for (const segment of segments) {
|
|
194
|
+
if (INTERNAL_API_CONTEXT_RE.test(segment)) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const candidate = extractCandidate(segment, [
|
|
198
|
+
/\b(?:integrate|integration with|sync|sync with)\s+(?:with\s+)?([a-z][a-z0-9-]{1,40})\b/i,
|
|
199
|
+
/\b([a-z][a-z0-9-]{1,40})\s+(?:api|webhook|sdk|integration)\b/i,
|
|
200
|
+
]);
|
|
201
|
+
if (candidate !== null && isAffirmedCandidate(segment, candidate)) {
|
|
202
|
+
integrations.push(normalizeCandidate(candidate));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return Array.from(new Set(integrations));
|
|
206
|
+
}
|
|
207
|
+
function extractCandidate(segment, patterns) {
|
|
208
|
+
for (const pattern of patterns) {
|
|
209
|
+
const match = pattern.exec(segment);
|
|
210
|
+
const candidate = match?.[1];
|
|
211
|
+
if (candidate && !GENERIC_CANDIDATES.has(candidate.toLowerCase())) {
|
|
212
|
+
return candidate;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
function isAffirmedCandidate(segment, candidate) {
|
|
218
|
+
const index = segment.toLowerCase().indexOf(candidate.toLowerCase());
|
|
219
|
+
const prefix = index < 0 ? segment : segment.slice(0, index);
|
|
220
|
+
return !NEGATION_RE.test(prefix) && !EXAMPLE_RE.test(prefix) && !TEST_PROSE_RE.test(prefix);
|
|
91
221
|
}
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
return Array.from(new Set(matches.map((match) => match.replace(/\s+(api|webhook|sdk|integration)$/i, ''))));
|
|
222
|
+
function normalizeCandidate(candidate) {
|
|
223
|
+
return candidate.charAt(0).toUpperCase() + candidate.slice(1);
|
|
95
224
|
}
|
|
96
225
|
//# sourceMappingURL=answer-extractor.js.map
|