@planu/cli 4.11.0 → 4.11.2
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/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-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/keyword-matcher/stopwords.d.ts +2 -2
- 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/metadata-value-policy.d.ts +20 -20
- package/dist/engine/text-signal-boundaries.js +45 -12
- package/dist/engine/universal-rules/rules/planu-modes.js +0 -2
- 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/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 +39 -118
- package/dist/tools/compliance-gate-handler.d.ts +1 -1
- package/dist/tools/create-spec.js +4 -24
- package/dist/tools/delete-first/suggest-deletions.d.ts +1 -1
- 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/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/tool-registry/deprecated-stubs.d.ts +1 -1
- package/dist/tools/triage-request.d.ts +3 -3
- 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 +28 -27
- 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.js +1 -1
- 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/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/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/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/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.2] - 2026-07-10
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
- fix(specs): ground lifecycle output and adopt TypeScript 7
|
|
5
|
+
|
|
6
|
+
### Chores
|
|
7
|
+
- chore(deps): update patch dependencies
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [4.11.1] - 2026-07-09
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
- fix: benchmark representative graph sources
|
|
14
|
+
- fix: verify release lockfile with pinned pnpm
|
|
15
|
+
|
|
16
|
+
|
|
1
17
|
## [4.11.0] - 2026-07-09
|
|
2
18
|
|
|
3
19
|
### Features
|
|
@@ -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
|
|
@@ -3,12 +3,11 @@ export function detectDecisionGaps(signals, _projectContext) {
|
|
|
3
3
|
const gaps = [
|
|
4
4
|
buildPermissionGap(signals),
|
|
5
5
|
buildProviderGap(signals),
|
|
6
|
-
buildBillingModelGap(signals),
|
|
7
6
|
buildDataGap(signals),
|
|
8
7
|
buildFailureGap(signals),
|
|
9
8
|
].filter((gap) => gap !== null);
|
|
10
|
-
if (gaps.length === 0 &&
|
|
11
|
-
gaps.push(
|
|
9
|
+
if (gaps.length === 0 && isShortAmbiguous(signals)) {
|
|
10
|
+
gaps.push(buildScopeGap(signals));
|
|
12
11
|
}
|
|
13
12
|
return gaps.slice(0, MAX_GAPS);
|
|
14
13
|
}
|
|
@@ -18,16 +17,19 @@ function buildPermissionGap(signals) {
|
|
|
18
17
|
}
|
|
19
18
|
const subject = formatSubject(signals);
|
|
20
19
|
return makeGap({
|
|
21
|
-
id: 'permission-
|
|
20
|
+
id: 'permission-boundary',
|
|
22
21
|
kind: 'permission',
|
|
23
|
-
header: '
|
|
24
|
-
question: `For ${subject},
|
|
25
|
-
evidence: evidenceFrom(signals, ['permission
|
|
22
|
+
header: 'Access',
|
|
23
|
+
question: `For ${subject}, which access boundary is intended? This changes authorization checks and audit criteria.`,
|
|
24
|
+
evidence: evidenceFrom(signals, ['capability:permission']),
|
|
26
25
|
impact: 'Changes authorization checks and acceptance criteria.',
|
|
27
26
|
options: options([
|
|
28
|
-
['
|
|
29
|
-
[
|
|
30
|
-
|
|
27
|
+
['Existing access boundary', 'Apply the access boundary already defined by the project.'],
|
|
28
|
+
[
|
|
29
|
+
'New access boundary',
|
|
30
|
+
'Define the permitted actor without assuming a role or identity model.',
|
|
31
|
+
],
|
|
32
|
+
['Access control out of scope', 'Keep authorization changes outside this spec.'],
|
|
31
33
|
]),
|
|
32
34
|
});
|
|
33
35
|
}
|
|
@@ -37,38 +39,21 @@ function buildProviderGap(signals) {
|
|
|
37
39
|
}
|
|
38
40
|
const subject = formatSubject(signals);
|
|
39
41
|
return makeGap({
|
|
40
|
-
id: 'payment-provider',
|
|
42
|
+
id: 'payment-provider-boundary',
|
|
41
43
|
kind: 'provider',
|
|
42
44
|
header: 'Provider',
|
|
43
|
-
question: `For ${subject},
|
|
44
|
-
evidence: evidenceFrom(signals, ['billing
|
|
45
|
-
impact: 'Changes integration
|
|
45
|
+
question: `For ${subject}, is payment-provider selection part of this spec? This changes integration ownership and acceptance criteria.`,
|
|
46
|
+
evidence: evidenceFrom(signals, ['domain:billing']),
|
|
47
|
+
impact: 'Changes integration ownership and acceptance criteria.',
|
|
46
48
|
options: options([
|
|
47
|
-
['Stripe (Recommended)', 'Use the most common provider for subscription and checkout flows.'],
|
|
48
|
-
['PayPal', 'Use PayPal checkout and payment APIs.'],
|
|
49
49
|
[
|
|
50
|
-
'
|
|
51
|
-
'
|
|
50
|
+
'Provider selection in scope',
|
|
51
|
+
'Define selection criteria without choosing an unevidenced vendor.',
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
'Provider selection out of scope',
|
|
55
|
+
'Limit the spec to behavior that does not choose a provider.',
|
|
52
56
|
],
|
|
53
|
-
]),
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
function buildBillingModelGap(signals) {
|
|
57
|
-
if (!signals.hasBilling) {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
const subject = formatSubject(signals);
|
|
61
|
-
return makeGap({
|
|
62
|
-
id: 'billing-model',
|
|
63
|
-
kind: 'billing-model',
|
|
64
|
-
header: 'Billing',
|
|
65
|
-
question: `For ${subject}, is the billing behavior recurring, one-time, or invoice-based? This changes states, events, and acceptance criteria.`,
|
|
66
|
-
evidence: evidenceFrom(signals, ['billing/payment wording']),
|
|
67
|
-
impact: 'Changes lifecycle states and acceptance criteria.',
|
|
68
|
-
options: options([
|
|
69
|
-
['Recurring subscription (Recommended)', 'Model ongoing subscriptions and renewal states.'],
|
|
70
|
-
['One-time checkout', 'Model a single payment completion flow.'],
|
|
71
|
-
['Invoice-based', 'Model invoice creation, payment, and overdue states.'],
|
|
72
57
|
]),
|
|
73
58
|
});
|
|
74
59
|
}
|
|
@@ -78,81 +63,76 @@ function buildDataGap(signals) {
|
|
|
78
63
|
}
|
|
79
64
|
const subject = formatSubject(signals);
|
|
80
65
|
return makeGap({
|
|
81
|
-
id: 'data-
|
|
66
|
+
id: 'data-boundary',
|
|
82
67
|
kind: 'data',
|
|
83
68
|
header: 'Data',
|
|
84
|
-
question: `For ${subject},
|
|
85
|
-
evidence: evidenceFrom(signals, ['
|
|
86
|
-
impact: 'Changes
|
|
69
|
+
question: `For ${subject}, which data shape belongs in scope? This changes persistence, validation, and migration criteria.`,
|
|
70
|
+
evidence: evidenceFrom(signals, ['domain:data']),
|
|
71
|
+
impact: 'Changes persistence, validation, and migration criteria.',
|
|
87
72
|
options: options([
|
|
88
|
-
[
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
],
|
|
92
|
-
['New table or field', 'Add persistence changes and migration coverage.'],
|
|
93
|
-
['Read-only data', 'Do not persist new state for this behavior.'],
|
|
73
|
+
['Existing data shape', 'Use only data structures already present in the project.'],
|
|
74
|
+
['New data shape', 'Define the required data change without assuming a database product.'],
|
|
75
|
+
['Data change out of scope', 'Keep persistence changes outside this spec.'],
|
|
94
76
|
]),
|
|
95
77
|
});
|
|
96
78
|
}
|
|
97
79
|
function buildFailureGap(signals) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
!signals.riskTerms.includes('api')) {
|
|
80
|
+
const integration = signals.integrations[0];
|
|
81
|
+
if (integration === undefined) {
|
|
101
82
|
return null;
|
|
102
83
|
}
|
|
103
84
|
const subject = formatSubject(signals);
|
|
104
85
|
return makeGap({
|
|
105
|
-
id: 'failure-
|
|
86
|
+
id: 'failure-boundary',
|
|
106
87
|
kind: 'failure',
|
|
107
88
|
header: 'Failure',
|
|
108
|
-
question: `For ${subject},
|
|
109
|
-
evidence: evidenceFrom(signals,
|
|
110
|
-
impact: 'Changes error
|
|
89
|
+
question: `For ${subject}, should ${integration} failure behavior be in scope? This changes error-handling and verification criteria.`,
|
|
90
|
+
evidence: evidenceFrom(signals, [`integration:${integration}`]),
|
|
91
|
+
impact: 'Changes error-handling and verification criteria.',
|
|
111
92
|
options: options([
|
|
112
|
-
[
|
|
113
|
-
|
|
114
|
-
|
|
93
|
+
[
|
|
94
|
+
'Failure behavior in scope',
|
|
95
|
+
'Define the observable failure outcome without assuming a recovery strategy.',
|
|
96
|
+
],
|
|
97
|
+
['Failure behavior out of scope', 'Limit this spec to the successful integration boundary.'],
|
|
115
98
|
]),
|
|
116
99
|
});
|
|
117
100
|
}
|
|
118
|
-
function
|
|
101
|
+
function buildScopeGap(signals) {
|
|
119
102
|
const subject = formatSubject(signals);
|
|
120
103
|
return makeGap({
|
|
121
|
-
id: '
|
|
122
|
-
kind: '
|
|
123
|
-
header: '
|
|
124
|
-
question: `For ${subject},
|
|
125
|
-
evidence: evidenceFrom(signals, ['short
|
|
126
|
-
impact: 'Changes the
|
|
104
|
+
id: 'scope-outcome',
|
|
105
|
+
kind: 'scope',
|
|
106
|
+
header: 'Scope',
|
|
107
|
+
question: `For ${subject}, which observable outcome should define the scope? This changes the primary acceptance criterion.`,
|
|
108
|
+
evidence: evidenceFrom(signals, ['request:short-ambiguous']),
|
|
109
|
+
impact: 'Changes the primary acceptance criterion.',
|
|
127
110
|
options: options([
|
|
128
|
-
['
|
|
129
|
-
['
|
|
130
|
-
['
|
|
111
|
+
['Behavior change', 'Describe the concrete before-and-after behavior.'],
|
|
112
|
+
['Bounded investigation', 'Limit the work to a finding and its verification evidence.'],
|
|
113
|
+
['Maintenance outcome', 'Define the concrete cleanup or compatibility result.'],
|
|
131
114
|
]),
|
|
132
115
|
});
|
|
133
116
|
}
|
|
117
|
+
function isShortAmbiguous(signals) {
|
|
118
|
+
return (!signals.hasScope &&
|
|
119
|
+
!signals.hasTarget &&
|
|
120
|
+
!signals.hasBilling &&
|
|
121
|
+
!signals.hasDatabase &&
|
|
122
|
+
!signals.hasPermissionRisk &&
|
|
123
|
+
signals.integrations.length === 0);
|
|
124
|
+
}
|
|
134
125
|
function makeGap(gap) {
|
|
135
126
|
return { ...gap, multiSelect: gap.multiSelect ?? false, blocking: true };
|
|
136
127
|
}
|
|
137
128
|
function formatSubject(signals) {
|
|
138
129
|
const action = signals.action ?? 'this request';
|
|
139
|
-
|
|
140
|
-
return object === null ? action : `${action} ${object}`;
|
|
141
|
-
}
|
|
142
|
-
function formatIntegration(signals) {
|
|
143
|
-
if (signals.integrations.length > 0) {
|
|
144
|
-
return signals.integrations[0] ?? 'the integration';
|
|
145
|
-
}
|
|
146
|
-
if (signals.riskTerms.includes('sync')) {
|
|
147
|
-
return 'the sync';
|
|
148
|
-
}
|
|
149
|
-
return 'the API';
|
|
130
|
+
return signals.domainObject === null ? action : `${action} ${signals.domainObject}`;
|
|
150
131
|
}
|
|
151
132
|
function evidenceFrom(signals, extra) {
|
|
152
133
|
return [
|
|
153
134
|
...(signals.action ? [`action:${signals.action}`] : []),
|
|
154
135
|
...(signals.domainObject ? [`object:${signals.domainObject}`] : []),
|
|
155
|
-
...signals.riskTerms.map((term) => `risk:${term}`),
|
|
156
136
|
...extra,
|
|
157
137
|
].slice(0, 6);
|
|
158
138
|
}
|
|
@@ -5,7 +5,6 @@ export * from './question-generator.js';
|
|
|
5
5
|
export * from './capability.js';
|
|
6
6
|
export * from './elicit-helper.js';
|
|
7
7
|
export * from './answer-extractor.js';
|
|
8
|
-
export * from './option-builder.js';
|
|
9
8
|
export * from './user-elicitor.js';
|
|
10
9
|
export * from './decision-cache.js';
|
|
11
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -6,7 +6,6 @@ export * from './question-generator.js';
|
|
|
6
6
|
export * from './capability.js';
|
|
7
7
|
export * from './elicit-helper.js';
|
|
8
8
|
export * from './answer-extractor.js';
|
|
9
|
-
export * from './option-builder.js';
|
|
10
9
|
export * from './user-elicitor.js';
|
|
11
10
|
export * from './decision-cache.js';
|
|
12
11
|
//# sourceMappingURL=index.js.map
|