@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
@@ -1,34 +1,33 @@
1
1
  // tools/challenge-spec/scenarios-scale.ts — Traffic spike and data growth scenarios
2
- import { contentMentions } from './scenarios-utils.js';
2
+ import { contentMentions, detectChallengeCapabilities } from './scenarios-utils.js';
3
3
  export function generateScaleScenarios(spec, content, _knowledge) {
4
4
  const scenarios = [];
5
- const lower = content.toLowerCase();
5
+ const capabilities = detectChallengeCapabilities(spec, content);
6
+ if (!capabilities.scale) {
7
+ return scenarios;
8
+ }
6
9
  // High traffic
7
10
  scenarios.push({
8
- scenario: 'Traffic spike: 10x normal load',
11
+ scenario: 'Traffic spike exceeds the load envelope defined by the spec',
9
12
  probability: spec.scope === 'architectural' ? 'medium' : 'low',
10
13
  impact: 'high',
11
- currentHandling: contentMentions(lower, ['cache', 'cdn', 'queue', 'scale', 'auto-scale'])
14
+ currentHandling: contentMentions(content, ['cache', 'cdn', 'queue', 'scale', 'auto-scale'])
12
15
  ? 'Some scaling strategy mentioned'
13
16
  : 'Not addressed',
14
- requiredHandling: 'Implement caching (Redis/CDN). Use connection pooling. Consider auto-scaling. Load test before launch.',
15
- dataConsistency: 'Ensure cache invalidation strategy. Handle stale reads gracefully.',
16
- userExperience: 'Graceful degradation: serve cached content, queue non-critical operations.',
17
+ requiredHandling: 'Define a measurable load envelope and verify the declared scaling strategy.',
18
+ dataConsistency: 'Verify consistency behavior at the declared capacity boundary.',
19
+ userExperience: 'Use the degradation behavior stated in the service contract.',
17
20
  });
18
21
  // Data growth
19
- if (lower.includes('database') ||
20
- lower.includes('table') ||
21
- lower.includes('query') ||
22
- lower.includes('list') ||
23
- lower.includes('search')) {
22
+ if (capabilities.database) {
24
23
  scenarios.push({
25
- scenario: 'Database grows to millions of rows, queries slow down',
24
+ scenario: 'Database grows beyond the query-performance envelope',
26
25
  probability: 'medium',
27
26
  impact: 'medium',
28
- currentHandling: contentMentions(lower, ['index', 'pagination', 'cursor', 'partition'])
27
+ currentHandling: contentMentions(content, ['index', 'pagination', 'cursor', 'partition'])
29
28
  ? 'Some query optimization mentioned'
30
29
  : 'Not addressed',
31
- requiredHandling: 'Add database indexes on query columns. Use cursor-based pagination. Consider archiving old data.',
30
+ requiredHandling: 'Define expected data growth and verify the stated indexing and pagination plan.',
32
31
  dataConsistency: 'Indexes must be maintained. Monitor query plans.',
33
32
  userExperience: 'Consistent response times through pagination. No full-table scans.',
34
33
  });
@@ -1,3 +1,3 @@
1
1
  import type { FailureScenario, Spec, ProjectKnowledge } from '../../types/index.js';
2
- export declare function generateSecurityScenarios(_spec: Spec, content: string, _knowledge: ProjectKnowledge): FailureScenario[];
2
+ export declare function generateSecurityScenarios(spec: Spec, content: string, _knowledge: ProjectKnowledge): FailureScenario[];
3
3
  //# sourceMappingURL=scenarios-security.d.ts.map
@@ -1,5 +1,5 @@
1
1
  // tools/challenge-spec/scenarios-security.ts — Authentication, injection, and CSRF scenarios
2
- import { contentMentions, hasAnyAffirmedMatch } from './scenarios-utils.js';
2
+ import { contentMentions, detectChallengeCapabilities, hasAnyAffirmedMatch, } from './scenarios-utils.js';
3
3
  const AUTH_SIGNALS = [
4
4
  /\b(auth|authentication|login|session|token|jwt|oauth|permission|role|rbac)\b/i,
5
5
  ];
@@ -9,10 +9,11 @@ const INPUT_SIGNALS = [
9
9
  const STATE_CHANGING_WEB_SIGNALS = [
10
10
  /\b(form submit|post request|put request|delete request|mutation|state-changing|cookie auth)\b/i,
11
11
  ];
12
- export function generateSecurityScenarios(_spec, content, _knowledge) {
12
+ export function generateSecurityScenarios(spec, content, _knowledge) {
13
13
  const scenarios = [];
14
14
  const lower = content.toLowerCase();
15
15
  const text = content;
16
+ const capabilities = detectChallengeCapabilities(spec, content);
16
17
  // Authentication bypass
17
18
  if (hasAnyAffirmedMatch(text, AUTH_SIGNALS)) {
18
19
  scenarios.push({
@@ -22,23 +23,29 @@ export function generateSecurityScenarios(_spec, content, _knowledge) {
22
23
  currentHandling: contentMentions(lower, ['auth', 'jwt', 'session', 'middleware'])
23
24
  ? 'Authentication mentioned'
24
25
  : 'No explicit auth handling',
25
- requiredHandling: 'Validate auth tokens on every request. Implement RBAC. Never trust client-side auth state.',
26
- dataConsistency: 'Ensure RLS policies are enabled. No data leak through unauthorized queries.',
27
- userExperience: 'Redirect to login on 401. Clear error messages without leaking implementation details.',
26
+ requiredHandling: 'Validate identity at the declared trust boundary and enforce the authorization model named by the contract.',
27
+ dataConsistency: 'Unauthorized operations must not read or mutate protected state.',
28
+ userExperience: 'Use the unauthorized-access behavior defined by the client contract.',
28
29
  });
29
30
  }
30
31
  // Injection attacks
31
32
  if (hasAnyAffirmedMatch(text, INPUT_SIGNALS)) {
32
33
  scenarios.push({
33
- scenario: 'SQL injection / XSS / command injection via user input',
34
+ scenario: capabilities.database
35
+ ? 'SQL injection / XSS / command injection via user input'
36
+ : 'XSS / command injection via user input',
34
37
  probability: 'high',
35
38
  impact: 'critical',
36
39
  currentHandling: contentMentions(lower, ['sanitiz', 'escap', 'parameterized', 'prepared'])
37
40
  ? 'Some sanitization mentioned'
38
41
  : 'Not explicitly addressed',
39
- requiredHandling: 'Use parameterized queries (never string concatenation). Sanitize HTML output. Use CSP headers.',
40
- dataConsistency: 'Injection can corrupt or exfiltrate data. Use least-privilege DB connections.',
41
- userExperience: 'Transparent to user. Invalid input is silently sanitized.',
42
+ requiredHandling: capabilities.database
43
+ ? 'Use parameterized queries and sanitize output at the declared input boundary.'
44
+ : 'Sanitize output and constrain command execution at the declared input boundary.',
45
+ dataConsistency: capabilities.database
46
+ ? 'Use least-privilege database access for data reached through this input.'
47
+ : 'Invalid input must not reach state-changing operations.',
48
+ userExperience: 'Use the invalid-input response defined by the interaction contract.',
42
49
  });
43
50
  }
44
51
  // CSRF / rate limiting only applies when the spec explicitly involves auth or
@@ -52,9 +59,9 @@ export function generateSecurityScenarios(_spec, content, _knowledge) {
52
59
  currentHandling: contentMentions(lower, ['csrf', 'rate limit', 'throttle', 'captcha'])
53
60
  ? 'Some protection mentioned'
54
61
  : 'Not addressed',
55
- requiredHandling: 'Implement CSRF tokens for state-changing operations. Rate-limit sensitive endpoints. Add CAPTCHA for auth endpoints.',
62
+ requiredHandling: 'Define request-forgery and abuse controls from the authentication and mutation contracts.',
56
63
  dataConsistency: 'Prevent mass data modifications from automated attacks.',
57
- userExperience: 'Show rate limit message. Require CAPTCHA after N failed attempts.',
64
+ userExperience: 'Use the throttling and verification behavior defined by the auth contract.',
58
65
  });
59
66
  }
60
67
  return scenarios;
@@ -1,7 +1,33 @@
1
- /**
2
- * Returns true if any of the keywords appear in lowerContent (case-insensitive match).
3
- * lowerContent is expected to already be lowercased by the caller.
4
- */
5
- export declare function contentMentions(lowerContent: string, keywords: string[]): boolean;
1
+ import type { Spec } from '../../types/index.js';
2
+ export interface ChallengeCapabilities {
3
+ networkApi: boolean;
4
+ database: boolean;
5
+ authentication: boolean;
6
+ authorization: boolean;
7
+ userInput: boolean;
8
+ externalService: boolean;
9
+ payment: boolean;
10
+ fileTransfer: boolean;
11
+ scale: boolean;
12
+ dataWrite: boolean;
13
+ cache: boolean;
14
+ duplicateOperation: boolean;
15
+ concurrency: boolean;
16
+ privacy: boolean;
17
+ events: boolean;
18
+ implementationContract: boolean;
19
+ desktopUpdate: boolean;
20
+ agent: boolean;
21
+ ddd: boolean;
22
+ xp: boolean;
23
+ validation: boolean;
24
+ rateLimit: boolean;
25
+ saga: boolean;
26
+ platform: boolean;
27
+ }
28
+ /** Boundary-aware, affirmative replacement for advisory substring checks. */
29
+ export declare function contentMentions(content: string, keywords: string[]): boolean;
30
+ export declare function detectChallengeCapabilities(spec: Spec, specContent: string): ChallengeCapabilities;
31
+ export declare function hasAnyChallengeCapability(capabilities: ChallengeCapabilities, names: (keyof ChallengeCapabilities)[]): boolean;
6
32
  export { hasAffirmedMatch, hasAnyAffirmedMatch, stripNonContractText, } from '../../engine/text-signal-boundaries.js';
7
33
  //# sourceMappingURL=scenarios-utils.d.ts.map
@@ -1,10 +1,106 @@
1
1
  // tools/challenge-spec/scenarios-utils.ts — Shared utilities for scenario generators
2
- /**
3
- * Returns true if any of the keywords appear in lowerContent (case-insensitive match).
4
- * lowerContent is expected to already be lowercased by the caller.
5
- */
6
- export function contentMentions(lowerContent, keywords) {
7
- return keywords.some((kw) => lowerContent.includes(kw.toLowerCase()));
2
+ import { hasAnyAffirmedMatch } from '../../engine/text-signal-boundaries.js';
3
+ const CAPABILITY_SIGNALS = {
4
+ networkApi: [
5
+ /(?<!compiler\s)\bapi\b(?!\s+(?:compatibility|surface|package|type))/i,
6
+ /\b(?:public|external|remote|rest|http)\s+api\b/i,
7
+ /\bapi\s+(?:endpoint|route|request|response|server|client)\b/i,
8
+ /\b(?:http|rest)\s+(?:endpoint|route|request|response)\b/i,
9
+ /\bfetch(?:es|ing)?\s+(?:from\s+)?(?:an?\s+)?(?:endpoint|service|url)\b/i,
10
+ ],
11
+ database: [
12
+ /\b(?:database|postgres(?:ql)?|mysql|sqlite|mongodb|dynamodb|supabase)\b/i,
13
+ /\b(?:sql|nosql)\s+(?:query|statement|table|database)\b/i,
14
+ /\b(?:database|persistence)\s+(?:query|schema|table|migration|transaction)\b/i,
15
+ /\b(?:orm|prisma|drizzle|typeorm|sequelize)\b/i,
16
+ ],
17
+ authentication: [
18
+ /\b(?:auth(?:entication)?|login|sign[ -]?in|password|jwt|oauth2?|session\s+auth)\b/i,
19
+ ],
20
+ authorization: [
21
+ /\b(?:authori[sz]ation|permission|rbac|role-based|access\s+control|resource\s+ownership|tenant)\b/i,
22
+ ],
23
+ userInput: [
24
+ /\b(?:user|client)\s+input\b/i,
25
+ /\b(?:input\s+form|form\s+(?:input|submission)|request\s+body|query\s+parameter|uploaded\s+payload)\b/i,
26
+ /\binputs?\s+(?:are\s+)?validat/i,
27
+ ],
28
+ externalService: [
29
+ /\b(?:third-party|external|remote|downstream)\s+(?:service|provider|dependency|system)\b/i,
30
+ /\bwebhook\s+(?:provider|delivery|endpoint|request)\b/i,
31
+ ],
32
+ payment: [/\b(?:payment|billing|checkout|invoice|stripe|paypal)\b/i],
33
+ fileTransfer: [
34
+ /\b(?:(?:large|binary|uploaded)\s+)?(?:file|image|media|attachment)\s+(?:upload|download|transfer|stream)\b/i,
35
+ /\b(?:upload|download|stream)(?:ed|ing|s)?\s+(?:a\s+)?(?:(?:large|binary)\s+)?(?:file|image|media|attachment)\b/i,
36
+ /\bbatch\s+(?:import|export|processing|operation)\b/i,
37
+ /\bbatch\s+upload\b/i,
38
+ ],
39
+ scale: [
40
+ /\b(?:traffic|load|throughput|requests?\s+per\s+second|concurrent\s+users?)\b/i,
41
+ /\b(?:auto[ -]?scal|load\s+test|capacity\s+plan)/i,
42
+ /\b(?:data\s+growth|growing\s+dataset|query\s+performance\s+at\s+scale)\b/i,
43
+ ],
44
+ dataWrite: [
45
+ /\b(?:database\s+transaction|persist(?:s|ed|ing)?\s+(?:record|entity|data)|write(?:s|ing)?\s+(?:record|entity|database))\b/i,
46
+ /\b(?:state-changing|data\s+mutation|atomic\s+database\s+write)\b/i,
47
+ /\b(?:transaction|atomic\s+write|update\s+(?:record|state)|modify\s+(?:record|state))\b/i,
48
+ ],
49
+ cache: [/\b(?:cache|caching|cached\s+(?:data|response)|redis)\b/i],
50
+ duplicateOperation: [
51
+ /\b(?:double[ -]?click|duplicate\s+(?:request|submission|operation|record)|idempoten)/i,
52
+ ],
53
+ concurrency: [
54
+ /\b(?:concurren|race\s+condition|shared\s+state|parallel\s+write|atomic\s+increment|optimistic\s+lock)/i,
55
+ /\b(?:counter|increment|balance|stock|quantity|vote|status\s+(?:update|transition))\b/i,
56
+ /\b(?:file\s+upload|resource\s+manager|search\s+feed|list\s+endpoint|global\s+cache|session\s+state|cart\s+state|draft\s+state)\b/i,
57
+ ],
58
+ privacy: [
59
+ /\b(?:pii|personal\s+data|personal\s+information|privacy|gdpr|hipaa|data\s+subject)\b/i,
60
+ /\b(?:email\s+address|phone\s+number|social\s+security\s+number)\b/i,
61
+ ],
62
+ events: [
63
+ /\b(?:process|handle|publish|consume)\w*(?:\s+[a-z0-9_-]+){0,3}\s+event\b/i,
64
+ /\b(?:event\s+(?:schema|contract|producer|consumer|handler|stream)|message\s+(?:broker|queue|consumer|producer))\b/i,
65
+ /\b(?:kafka|rabbitmq|pubsub|nats|eventbridge|sqs|sns|dead[ -]?letter\s+queue|dlq)\b/i,
66
+ /\b(?:publish|produce|consume)(?:es|d|r|rs|ing)?\s+(?:an?\s+)?(?:event|message)\b/i,
67
+ ],
68
+ implementationContract: [
69
+ /\bimplementation\s+contract\b/i,
70
+ /\b(?:file-level\s+work\s+plan|acceptance-to-verification\s+map|behavior\s+contract)\b/i,
71
+ ],
72
+ desktopUpdate: [
73
+ /\b(?:desktop|electron|tauri)\s+(?:app\s+)?(?:auto[ -]?)?update\b/i,
74
+ /\b(?:auto[ -]?updater|electron-updater)\b/i,
75
+ ],
76
+ agent: [/\b(?:ai\s+agent|agent\s+contract|llm\s+agent|tool\s+call|autonomous\s+agent)\b/i],
77
+ ddd: [/\b(?:domain-driven\s+design|ddd\s+aggregate|aggregate\s+root|bounded\s+context)\b/i],
78
+ xp: [/\b(?:pair\s+programming|extreme\s+programming|xp\s+pair)\b/i],
79
+ validation: [/\b(?:input|payload|request|schema)\s+validat/i, /\b(?:zod|joi|json\s+schema)\b/i],
80
+ rateLimit: [/\b(?:rate[ -]?limit|throttl|request\s+quota)\b/i],
81
+ saga: [/\b(?:saga|compensating\s+transaction|distributed\s+transaction)\b/i],
82
+ platform: [
83
+ /\b(?:smart\s+contract|solidity|discord\s+bot|telegram\s+bot|iot\s+device|firmware|infrastructure\s+as\s+code|terraform|machine\s+learning\s+model)\b/i,
84
+ ],
85
+ };
86
+ function keywordPattern(keyword) {
87
+ const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s+/g, '\\s+');
88
+ const suffix = /^[a-z0-9]+$/i.test(keyword) && keyword.length > 3 ? '[a-z0-9_-]*' : '';
89
+ return new RegExp(`\\b${escaped}${suffix}\\b`, 'i');
90
+ }
91
+ /** Boundary-aware, affirmative replacement for advisory substring checks. */
92
+ export function contentMentions(content, keywords) {
93
+ return hasAnyAffirmedMatch(content, keywords.map(keywordPattern));
94
+ }
95
+ export function detectChallengeCapabilities(spec, specContent) {
96
+ const text = `${spec.title}\n${spec.tags.join(' ')}\n${specContent}`;
97
+ return Object.fromEntries(Object.entries(CAPABILITY_SIGNALS).map(([capability, patterns]) => [
98
+ capability,
99
+ hasAnyAffirmedMatch(text, patterns),
100
+ ]));
101
+ }
102
+ export function hasAnyChallengeCapability(capabilities, names) {
103
+ return names.some((name) => capabilities[name]);
8
104
  }
9
105
  export { hasAffirmedMatch, hasAnyAffirmedMatch, stripNonContractText, } from '../../engine/text-signal-boundaries.js';
10
106
  //# sourceMappingURL=scenarios-utils.js.map
@@ -3,7 +3,7 @@ import type { FailureScenario, Spec, ProjectKnowledge } from '../../types/index.
3
3
  * Generate security challenge scenarios specific to authorization, STRIDE threats,
4
4
  * and resource ownership. Used by handleChallengeSpec in the 'security' focus area.
5
5
  */
6
- export declare function generateSecurityChallengeScenarios(spec: Spec, _specContent: string, _knowledge: ProjectKnowledge): FailureScenario[];
6
+ export declare function generateSecurityChallengeScenarios(spec: Spec, specContent: string, _knowledge: ProjectKnowledge): FailureScenario[];
7
7
  /**
8
8
  * Generate security test case scaffolds for auth/API features.
9
9
  * SPEC-029 criterion 12: auto-generate security test scaffolds.
@@ -1,12 +1,13 @@
1
1
  // tools/challenge-spec/security-challenge-scenarios.ts — Security challenges for challenge_spec
2
2
  // SPEC-029 criteria 11, 24: authorization challenges and STRIDE-based challenges
3
3
  import { detectSecurityProfiles } from '../../engine/security-analyzer.js';
4
+ import { hasAnyAffirmedMatch } from './scenarios-utils.js';
4
5
  /**
5
6
  * Generate security challenge scenarios specific to authorization, STRIDE threats,
6
7
  * and resource ownership. Used by handleChallengeSpec in the 'security' focus area.
7
8
  */
8
- export function generateSecurityChallengeScenarios(spec, _specContent, _knowledge) {
9
- const profiles = detectSecurityProfiles(spec.title, '', spec.tags);
9
+ export function generateSecurityChallengeScenarios(spec, specContent, _knowledge) {
10
+ const profiles = detectSecurityProfiles(spec.title, specContent, spec.tags);
10
11
  const scenarios = [];
11
12
  // Authorization challenge (criterion 11): resource ownership
12
13
  if (profiles.includes('public-api') || profiles.includes('db-access')) {
@@ -16,15 +17,14 @@ export function generateSecurityChallengeScenarios(spec, _specContent, _knowledg
16
17
  impact: 'critical',
17
18
  currentHandling: 'Authorization check may be missing at the data access layer — only route-level guards present.',
18
19
  requiredHandling: 'Implement object-level authorization on every data access operation. ' +
19
- 'Never trust client-provided resource IDs without verifying ownership. ' +
20
- 'Use indirect references (UUIDs) instead of sequential IDs.',
21
- dataConsistency: 'Unauthorized access should return 403, never 404 that leaks resource existence.',
22
- userExperience: 'Return consistent 403 Forbidden for unauthorized access — do not reveal if the resource exists.',
20
+ 'Never trust client-provided resource identifiers without verifying ownership using the declared authorization model.',
21
+ dataConsistency: 'Unauthorized access must not reveal resource existence.',
22
+ userExperience: 'Use the authorization failure response defined by the API contract.',
23
23
  });
24
24
  }
25
25
  // JWT-specific challenges
26
- const lower = `${spec.title} ${spec.tags.join(' ')}`.toLowerCase();
27
- const hasJwt = lower.includes('jwt') || lower.includes('token') || lower.includes('auth');
26
+ const signalText = `${spec.title}\n${spec.tags.join(' ')}\n${specContent}`;
27
+ const hasJwt = hasAnyAffirmedMatch(signalText, [/\b(?:jwt|json\s+web\s+token)\b/i]);
28
28
  if (hasJwt) {
29
29
  scenarios.push({
30
30
  scenario: `[${spec.id}] JWT Algorithm Confusion Attack: attacker changes JWT header algorithm to "none" or switches RS256 to HS256. Server accepts forged token if it trusts the header.`,
@@ -34,17 +34,16 @@ export function generateSecurityChallengeScenarios(spec, _specContent, _knowledg
34
34
  requiredHandling: 'Explicitly configure the allowed algorithm(s) in JWT verification. ' +
35
35
  'Never use the algorithm from the token header. Reject tokens with "none" algorithm.',
36
36
  dataConsistency: 'Invalid tokens must never reach business logic — reject at middleware boundary.',
37
- userExperience: 'Return 401 Unauthorized for invalid tokens do not expose algorithm validation details.',
37
+ userExperience: 'Use the invalid-token response defined by the authentication contract.',
38
38
  });
39
39
  scenarios.push({
40
- scenario: `[${spec.id}] Expired Token Access: user presents an expired JWT. Does the API return 401 immediately, or does expired token reach business logic?`,
40
+ scenario: `[${spec.id}] Expired Token Access: user presents an expired JWT. Does the declared authentication failure occur before business logic?`,
41
41
  probability: 'medium',
42
42
  impact: 'high',
43
43
  currentHandling: 'Expiry check may not be the first middleware step — race conditions possible.',
44
- requiredHandling: 'Verify token expiry (exp claim) as the first step in auth middleware. ' +
45
- 'Return 401 with error code "token_expired" so clients can trigger refresh flow.',
44
+ requiredHandling: 'Verify token expiry before business logic and use the expiration result defined by the authentication contract.',
46
45
  dataConsistency: 'Expired tokens must never modify state — check before any write operations.',
47
- userExperience: 'Return structured error { "error": "token_expired" } so clients can refresh automatically.',
46
+ userExperience: 'Use the expiration and refresh behavior defined by the client contract.',
48
47
  });
49
48
  }
50
49
  // SQL injection challenges for DB features
@@ -62,28 +61,24 @@ export function generateSecurityChallengeScenarios(spec, _specContent, _knowledg
62
61
  });
63
62
  }
64
63
  // Role-based access challenges
65
- const hasRoles = lower.includes('role') ||
66
- lower.includes('admin') ||
67
- lower.includes('permission') ||
68
- lower.includes('rbac');
64
+ const hasRoles = hasAnyAffirmedMatch(signalText, [
65
+ /\b(?:role|admin|permission|rbac|access\s+control)\b/i,
66
+ ]);
69
67
  if (hasRoles) {
70
68
  scenarios.push({
71
69
  scenario: `[${spec.id}] Privilege Escalation via Role Manipulation: user with role "viewer" calls admin endpoint, or modifies JWT payload claiming role "admin". Role enforcement missing at middleware.`,
72
70
  probability: 'medium',
73
71
  impact: 'critical',
74
72
  currentHandling: 'Role enforcement may be per-handler instead of middleware — inconsistent coverage.',
75
- requiredHandling: 'Enforce RBAC in middleware, not per-handler. ' +
76
- 'Never derive roles from user-supplied data; always load from a trusted store. ' +
77
- 'Log unauthorized access attempts for monitoring.',
73
+ requiredHandling: 'Enforce the declared role model consistently before protected behavior, load roles from the trusted identity boundary, and record the audit evidence required by the contract.',
78
74
  dataConsistency: 'Admin operations that modify state must verify role before any data mutation.',
79
- userExperience: 'Return 403 Forbidden with a user-facing message log the attempt with user ID for audit.',
75
+ userExperience: 'Use the forbidden-action response defined by the authorization contract.',
80
76
  });
81
77
  }
82
78
  // STRIDE elevation of privilege challenge for admin/payment
83
- const isHighRisk = lower.includes('payment') ||
84
- lower.includes('admin') ||
85
- lower.includes('billing') ||
86
- lower.includes('sensitive');
79
+ const isHighRisk = hasAnyAffirmedMatch(signalText, [
80
+ /\b(?:payment|billing|admin\s+operation|sensitive\s+operation)\b/i,
81
+ ]);
87
82
  if (isHighRisk) {
88
83
  scenarios.push({
89
84
  scenario: `[${spec.id}] STRIDE Elevation of Privilege: regular user performs a sequence of legitimate API calls that results in unintended elevated access (e.g., create → transfer → access with original permissions).`,