@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.
Files changed (155) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/config/elicitation-questions.json +5 -17
  3. package/dist/engine/cascade-hooks/registry.js +0 -2
  4. package/dist/engine/challenge-scenarios-loader.d.ts +3 -3
  5. package/dist/engine/compliance-injector.d.ts +3 -3
  6. package/dist/engine/dashboard/kanban-logic.d.ts +1 -1
  7. package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts +1 -1
  8. package/dist/engine/doc-generator/proposal/proposal-generator.d.ts +1 -1
  9. package/dist/engine/elicitation/answer-extractor.d.ts +1 -4
  10. package/dist/engine/elicitation/answer-extractor.js +197 -68
  11. package/dist/engine/elicitation/decision-gap-detector.js +59 -79
  12. package/dist/engine/elicitation/index.d.ts +0 -1
  13. package/dist/engine/elicitation/index.js +0 -1
  14. package/dist/engine/elicitation/question-generator.js +10 -3
  15. package/dist/engine/elicitation/question-grounding-gate.js +83 -34
  16. package/dist/engine/evidence-gates/lifecycle-gate.js +27 -14
  17. package/dist/engine/evidence-index/index-builder.js +7 -2
  18. package/dist/engine/frontmatter-sha/frozen-fields.d.ts +1 -1
  19. package/dist/engine/handoff-artifacts/schemas/red-tests-handoff.d.ts +2 -2
  20. package/dist/engine/handoff-artifacts/schemas.d.ts +22 -22
  21. package/dist/engine/human-summary.js +3 -3
  22. package/dist/engine/implementation-contract/common.d.ts +1 -1
  23. package/dist/engine/implementation-contract/renderer.js +17 -39
  24. package/dist/engine/keyword-matcher/stopwords.d.ts +2 -2
  25. package/dist/engine/lifecycle-hints.d.ts +1 -1
  26. package/dist/engine/lifecycle-hints.js +13 -29
  27. package/dist/engine/provider-adapters/adapters/claude.d.ts +1 -1
  28. package/dist/engine/provider-adapters/adapters/gemini.d.ts +1 -1
  29. package/dist/engine/provider-adapters/adapters/gpt4.d.ts +1 -1
  30. package/dist/engine/provider-adapters/adapters/markdown.d.ts +1 -1
  31. package/dist/engine/safety/atomic-write-file.d.ts +1 -1
  32. package/dist/engine/safety/cross-process-lock.d.ts +1 -1
  33. package/dist/engine/safety/path-sanitizer.d.ts +1 -1
  34. package/dist/engine/security-analyzer/profile-detector.js +48 -16
  35. package/dist/engine/spec-format/copula-blacklist.d.ts +2 -2
  36. package/dist/engine/spec-format/lean-spec-generator.d.ts +2 -2
  37. package/dist/engine/spec-format/lean-spec-generator.js +9 -10
  38. package/dist/engine/spec-format/metadata-value-policy.d.ts +20 -20
  39. package/dist/engine/spec-generator/fallback-generator.js +13 -20
  40. package/dist/engine/spec-grounding/contract.d.ts +5 -0
  41. package/dist/engine/spec-grounding/contract.js +10 -6
  42. package/dist/engine/spec-migrator/lean-migration.js +9 -16
  43. package/dist/engine/text-signal-boundaries.js +45 -12
  44. package/dist/engine/universal-rules/rules/planu-modes.js +0 -2
  45. package/dist/engine/web-fetcher/stack-advisor.d.ts +2 -2
  46. package/dist/engine/web-fetcher/stack-advisor.js +78 -17
  47. package/dist/engine/well-known/manifest-schema.d.ts +33 -33
  48. package/dist/engine/workers/schema.d.ts +3 -3
  49. package/dist/hosts/claude-code/ux/mcp-resources.d.ts +4 -4
  50. package/dist/hosts/codex/rest-shim.d.ts +1 -1
  51. package/dist/native/lightweight-command-catalog.d.ts +30 -30
  52. package/dist/server/openapi-spec.d.ts +132 -132
  53. package/dist/tools/challenge-spec/challenge-report.d.ts +13 -0
  54. package/dist/tools/challenge-spec/challenge-report.js +55 -0
  55. package/dist/tools/challenge-spec/event-challenge-scenarios.js +8 -27
  56. package/dist/tools/challenge-spec/resilience-challenge-scenarios-a.js +16 -32
  57. package/dist/tools/challenge-spec/resilience-challenge-scenarios-b.js +15 -20
  58. package/dist/tools/challenge-spec/scenario-collector.d.ts +13 -0
  59. package/dist/tools/challenge-spec/scenario-collector.js +86 -0
  60. package/dist/tools/challenge-spec/scenarios-data.js +10 -9
  61. package/dist/tools/challenge-spec/scenarios-failure.js +38 -54
  62. package/dist/tools/challenge-spec/scenarios-scale.js +14 -15
  63. package/dist/tools/challenge-spec/scenarios-security.d.ts +1 -1
  64. package/dist/tools/challenge-spec/scenarios-security.js +18 -11
  65. package/dist/tools/challenge-spec/scenarios-utils.d.ts +31 -5
  66. package/dist/tools/challenge-spec/scenarios-utils.js +102 -6
  67. package/dist/tools/challenge-spec/security-challenge-scenarios.d.ts +1 -1
  68. package/dist/tools/challenge-spec/security-challenge-scenarios.js +20 -25
  69. package/dist/tools/challenge-spec.js +46 -124
  70. package/dist/tools/clarify-requirements/multiple-choice.js +1 -1
  71. package/dist/tools/clarify-requirements/questions-context.js +11 -11
  72. package/dist/tools/clarify-requirements/questions.js +3 -23
  73. package/dist/tools/clarify-requirements.js +38 -0
  74. package/dist/tools/compliance-gate-handler.d.ts +1 -1
  75. package/dist/tools/create-spec/autopilot-analyzer.d.ts +1 -1
  76. package/dist/tools/create-spec/autopilot-analyzer.js +29 -54
  77. package/dist/tools/create-spec/post-creation.js +23 -4
  78. package/dist/tools/create-spec.js +51 -168
  79. package/dist/tools/delete-first/suggest-deletions.d.ts +1 -1
  80. package/dist/tools/elicit-requirements-handler.js +20 -12
  81. package/dist/tools/generate-batch-script.d.ts +2 -2
  82. package/dist/tools/init-project/rules-generator.js +0 -1
  83. package/dist/tools/oauth-handler.d.ts +1 -1
  84. package/dist/tools/register-sdd-tools.d.ts +1 -1
  85. package/dist/tools/register-spec-tools/core-spec-tools.js +11 -2
  86. package/dist/tools/render-spec-for-provider.d.ts +2 -2
  87. package/dist/tools/scan-orphan-spec-refs.d.ts +6 -6
  88. package/dist/tools/schemas/agents.d.ts +7 -7
  89. package/dist/tools/schemas/analysis.d.ts +11 -11
  90. package/dist/tools/schemas/delete.d.ts +5 -5
  91. package/dist/tools/schemas/github.d.ts +4 -4
  92. package/dist/tools/schemas/ide-config.d.ts +12 -12
  93. package/dist/tools/schemas/infra.d.ts +9 -9
  94. package/dist/tools/schemas/lifecycle.d.ts +50 -50
  95. package/dist/tools/schemas/llm-provider-schemas.d.ts +7 -7
  96. package/dist/tools/schemas/plugins-schemas.d.ts +12 -12
  97. package/dist/tools/schemas/project-graph.d.ts +6 -6
  98. package/dist/tools/schemas/rbac.d.ts +2 -2
  99. package/dist/tools/schemas/registry.d.ts +3 -3
  100. package/dist/tools/schemas/runtime-security.d.ts +2 -2
  101. package/dist/tools/schemas/session.d.ts +2 -2
  102. package/dist/tools/schemas/spec.d.ts +15 -15
  103. package/dist/tools/schemas/token-intelligence.d.ts +6 -6
  104. package/dist/tools/schemas/token-optimization.d.ts +2 -2
  105. package/dist/tools/schemas/workers-schema.d.ts +4 -4
  106. package/dist/tools/skill-bootstrap-handler.d.ts +7 -7
  107. package/dist/tools/suggest-stack.js +49 -13
  108. package/dist/tools/tool-registry/deprecated-stubs.d.ts +1 -1
  109. package/dist/tools/triage-request.d.ts +3 -3
  110. package/dist/tools/update-status/dod-gates.d.ts +8 -0
  111. package/dist/tools/update-status/dod-gates.js +68 -43
  112. package/dist/tools/update-status/file-sync.d.ts +1 -1
  113. package/dist/tools/update-status/file-sync.js +10 -45
  114. package/dist/tools/update-status/index.js +133 -97
  115. package/dist/tools/update-status/qa-gate.js +26 -0
  116. package/dist/tools/update-status/response-builder.d.ts +1 -1
  117. package/dist/tools/update-status/response-builder.js +11 -19
  118. package/dist/tools/update-status/transition-guard.d.ts +2 -2
  119. package/dist/tools/update-status/transition-guard.js +11 -8
  120. package/dist/tools/update-status-actions.d.ts +0 -1
  121. package/dist/tools/update-status-actions.js +10 -26
  122. package/dist/tools/update-status-convention-gate.d.ts +1 -6
  123. package/dist/tools/update-status-convention-gate.js +9 -33
  124. package/dist/tools/validate-lint.d.ts +1 -1
  125. package/dist/tools/validate-lint.js +39 -1
  126. package/dist/types/docs.d.ts +0 -4
  127. package/dist/types/env.d.ts +0 -4
  128. package/dist/types/estimation.d.ts +0 -2
  129. package/dist/types/multi-teammate-review.d.ts +4 -4
  130. package/dist/types/plugin-install.d.ts +7 -7
  131. package/dist/types/spec/core.d.ts +20 -0
  132. package/dist/types/spec/inputs.d.ts +1 -1
  133. package/dist/types/stack/index.d.ts +1 -1
  134. package/dist/types/stack/recommend.d.ts +10 -0
  135. package/package.json +21 -18
  136. package/planu-native.json +1 -1
  137. package/planu-plugin.json +1 -1
  138. package/src/i18n/messages/en.json +0 -72
  139. package/src/i18n/messages/es.json +0 -72
  140. package/src/i18n/messages/pt.json +0 -72
  141. package/dist/config/elicitation-dimensions.json +0 -51
  142. package/dist/engine/actuals-estimator.d.ts +0 -14
  143. package/dist/engine/actuals-estimator.js +0 -64
  144. package/dist/engine/cascade-hooks/hooks/living-specs.hook.d.ts +0 -3
  145. package/dist/engine/cascade-hooks/hooks/living-specs.hook.js +0 -19
  146. package/dist/engine/elicitation/non-interactive-defaults.d.ts +0 -9
  147. package/dist/engine/elicitation/non-interactive-defaults.js +0 -28
  148. package/dist/engine/elicitation/option-builder.d.ts +0 -9
  149. package/dist/engine/elicitation/option-builder.js +0 -85
  150. package/dist/engine/progress-writer.d.ts +0 -10
  151. package/dist/engine/progress-writer.js +0 -63
  152. package/dist/tools/clarify-requirements/interview-mode.d.ts +0 -42
  153. package/dist/tools/clarify-requirements/interview-mode.js +0 -342
  154. package/dist/tools/update-status/mode-hints.d.ts +0 -4
  155. 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": "Who are the primary users of \"{title}\"?",
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": "How will you know when this feature is successfully implemented?",
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": "What should happen in error or edge cases?",
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": "What data needs to be stored or retrieved for this feature?",
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 a new API endpoint or changes to existing ones?",
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 ["draft", "review", "approved", "implementing", "done", "discarded"];
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 ["low", "medium", "high", "very_high", "critical"];
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 ["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"];
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: Extracts pre-existing answers from description to suppress redundant questions.
3
- const FRONTEND_TERMS = /\b(component|ui|page|css|style|layout|form|button|modal|dialog)\b/;
4
- const BACKEND_TERMS = /\b(api|endpoint|route|handler|migration|query|rpc|action|middleware)\b/;
5
- const INFRA_TERMS = /\b(deploy|ci|cd|docker|pipeline|config|env)\b/;
6
- const BILLING_TERMS = /\b(payment|billing|invoice|subscription|checkout|pricing|pay)\b/;
7
- const DATABASE_TERMS = /\b(database|schema|migration|table|query|sql|rls|rpc|persist|persistence|data access)\b/;
8
- const ACTION_RE = /\b(add|build|create|fix|update|remove|delete|integrate|sync|import|export|approve|reject|upload|download|migrate|configure|validate)\b\s+(?:(?:a|an|the|new)\s+)?([a-z0-9][a-z0-9 -]{1,60})/i;
9
- const ACTOR_TERMS = /\b(admin|administrator|manager|owner|customer|user|member|team|approver)\b/gi;
10
- const DATA_OBJECT_TERMS = /\b(account|profile|request|invoice|subscription|payment|order|report|file|avatar|table|policy|record|settings)\b/gi;
11
- const PERMISSION_TERMS = /\b(approval|approve|reject|admin|permission|role|rls|policy|access|auth|login|oauth|owner|manager)\b/;
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
- * Scan a feature description for pre-existing answers.
24
- * Any signal set to true means the corresponding question can be suppressed.
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 lower = stripIncidentalReferences(description.toLowerCase());
28
- const hasTarget = FRONTEND_TERMS.test(lower) || BACKEND_TERMS.test(lower) || INFRA_TERMS.test(lower);
29
- const hasDatabase = DATABASE_TERMS.test(lower);
30
- const hasUi = FRONTEND_TERMS.test(lower);
31
- let namedProvider = null;
32
- for (const { name, pattern } of PAYMENT_PROVIDERS) {
33
- if (pattern.test(lower)) {
34
- namedProvider = name;
35
- break;
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 actors = uniqueMatches(lower, ACTOR_TERMS);
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(/`[^`]*(?:\/|\.\w{1,8})[^`]*`/g, ' ')
74
- .replace(/\b\S+\/\S+\b/g, ' ')
75
- .replace(/\b[\w-]+\.(?:ts|tsx|js|jsx|mjs|cjs|json|md|css|scss|py|go|rs|java|rb|php)\b/g, ' ');
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 uniqueMatches(text, pattern) {
88
- const flags = pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`;
89
- const globalPattern = new RegExp(pattern.source, flags);
90
- return Array.from(new Set(Array.from(text.matchAll(globalPattern)).map((match) => match[0])));
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 extractExternalIntegrations(text) {
93
- const matches = text.match(/\b([a-z][a-z0-9-]+)\s+(?:api|webhook|sdk|integration)\b/gi) ?? [];
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