@planu/cli 0.96.4 → 0.97.0
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/dist/cli/commands/install.d.ts.map +1 -1
- package/dist/cli/commands/install.js +70 -2
- package/dist/cli/commands/install.js.map +1 -1
- package/dist/config/compliance-profiles.json +62 -0
- package/dist/config/license-plans.json +19 -2
- package/dist/config/spec-templates/crud-rest-api/spec.md +35 -0
- package/dist/config/spec-templates/crud-rest-api/template.json +10 -0
- package/dist/config/spec-templates/email-notifications/spec.md +31 -0
- package/dist/config/spec-templates/email-notifications/template.json +10 -0
- package/dist/config/spec-templates/file-upload-s3/spec.md +31 -0
- package/dist/config/spec-templates/file-upload-s3/template.json +11 -0
- package/dist/config/spec-templates/jwt-auth/spec.md +35 -0
- package/dist/config/spec-templates/jwt-auth/template.json +10 -0
- package/dist/config/spec-templates/oauth-social-login/spec.md +31 -0
- package/dist/config/spec-templates/oauth-social-login/template.json +10 -0
- package/dist/config/spec-templates/rate-limiting/spec.md +31 -0
- package/dist/config/spec-templates/rate-limiting/template.json +10 -0
- package/dist/config/spec-templates/stripe-payments/spec.md +32 -0
- package/dist/config/spec-templates/stripe-payments/template.json +10 -0
- package/dist/config/spec-templates/webhook-system/spec.md +36 -0
- package/dist/config/spec-templates/webhook-system/template.json +10 -0
- package/dist/engine/agent-ready-exporter/formatters/devin.d.ts +7 -0
- package/dist/engine/agent-ready-exporter/formatters/devin.d.ts.map +1 -0
- package/dist/engine/agent-ready-exporter/formatters/devin.js +23 -0
- package/dist/engine/agent-ready-exporter/formatters/devin.js.map +1 -0
- package/dist/engine/agent-ready-exporter/formatters/generic.d.ts +7 -0
- package/dist/engine/agent-ready-exporter/formatters/generic.d.ts.map +1 -0
- package/dist/engine/agent-ready-exporter/formatters/generic.js +9 -0
- package/dist/engine/agent-ready-exporter/formatters/generic.js.map +1 -0
- package/dist/engine/agent-ready-exporter/formatters/kiro.d.ts +6 -0
- package/dist/engine/agent-ready-exporter/formatters/kiro.d.ts.map +1 -0
- package/dist/engine/agent-ready-exporter/formatters/kiro.js +34 -0
- package/dist/engine/agent-ready-exporter/formatters/kiro.js.map +1 -0
- package/dist/engine/agent-ready-exporter/formatters/swe-agent.d.ts +6 -0
- package/dist/engine/agent-ready-exporter/formatters/swe-agent.d.ts.map +1 -0
- package/dist/engine/agent-ready-exporter/formatters/swe-agent.js +40 -0
- package/dist/engine/agent-ready-exporter/formatters/swe-agent.js.map +1 -0
- package/dist/engine/agent-ready-exporter.d.ts +11 -0
- package/dist/engine/agent-ready-exporter.d.ts.map +1 -0
- package/dist/engine/agent-ready-exporter.js +86 -0
- package/dist/engine/agent-ready-exporter.js.map +1 -0
- package/dist/engine/compliance-checker.d.ts +19 -0
- package/dist/engine/compliance-checker.d.ts.map +1 -0
- package/dist/engine/compliance-checker.js +145 -0
- package/dist/engine/compliance-checker.js.map +1 -0
- package/dist/engine/generate-tests/generators/property-based-generator.d.ts +12 -0
- package/dist/engine/generate-tests/generators/property-based-generator.d.ts.map +1 -0
- package/dist/engine/generate-tests/generators/property-based-generator.js +159 -0
- package/dist/engine/generate-tests/generators/property-based-generator.js.map +1 -0
- package/dist/engine/jira-exporter.d.ts +39 -0
- package/dist/engine/jira-exporter.d.ts.map +1 -0
- package/dist/engine/jira-exporter.js +190 -0
- package/dist/engine/jira-exporter.js.map +1 -0
- package/dist/engine/linear-exporter.d.ts +32 -0
- package/dist/engine/linear-exporter.d.ts.map +1 -0
- package/dist/engine/linear-exporter.js +184 -0
- package/dist/engine/linear-exporter.js.map +1 -0
- package/dist/engine/planu-config-writer.d.ts +13 -2
- package/dist/engine/planu-config-writer.d.ts.map +1 -1
- package/dist/engine/planu-config-writer.js +32 -1
- package/dist/engine/planu-config-writer.js.map +1 -1
- package/dist/engine/property-test-generator.d.ts +14 -0
- package/dist/engine/property-test-generator.d.ts.map +1 -0
- package/dist/engine/property-test-generator.js +223 -0
- package/dist/engine/property-test-generator.js.map +1 -0
- package/dist/engine/skill-evaluator.d.ts +21 -0
- package/dist/engine/skill-evaluator.d.ts.map +1 -0
- package/dist/engine/skill-evaluator.js +126 -0
- package/dist/engine/skill-evaluator.js.map +1 -0
- package/dist/engine/spec-quality-scorer.d.ts +4 -0
- package/dist/engine/spec-quality-scorer.d.ts.map +1 -0
- package/dist/engine/spec-quality-scorer.js +334 -0
- package/dist/engine/spec-quality-scorer.js.map +1 -0
- package/dist/engine/spec-template-engine.d.ts +26 -0
- package/dist/engine/spec-template-engine.d.ts.map +1 -0
- package/dist/engine/spec-template-engine.js +127 -0
- package/dist/engine/spec-template-engine.js.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/storage/compliance-store.d.ts +11 -0
- package/dist/storage/compliance-store.d.ts.map +1 -0
- package/dist/storage/compliance-store.js +30 -0
- package/dist/storage/compliance-store.js.map +1 -0
- package/dist/storage/jira-store.d.ts +30 -0
- package/dist/storage/jira-store.d.ts.map +1 -0
- package/dist/storage/jira-store.js +84 -0
- package/dist/storage/jira-store.js.map +1 -0
- package/dist/storage/linear-store.d.ts +30 -0
- package/dist/storage/linear-store.d.ts.map +1 -0
- package/dist/storage/linear-store.js +84 -0
- package/dist/storage/linear-store.js.map +1 -0
- package/dist/tools/check-readiness.d.ts.map +1 -1
- package/dist/tools/check-readiness.js +8 -2
- package/dist/tools/check-readiness.js.map +1 -1
- package/dist/tools/compliance-handler.d.ts +6 -0
- package/dist/tools/compliance-handler.d.ts.map +1 -0
- package/dist/tools/compliance-handler.js +99 -0
- package/dist/tools/compliance-handler.js.map +1 -0
- package/dist/tools/eval-skill-handler.d.ts +4 -0
- package/dist/tools/eval-skill-handler.d.ts.map +1 -0
- package/dist/tools/eval-skill-handler.js +26 -0
- package/dist/tools/eval-skill-handler.js.map +1 -0
- package/dist/tools/export-spec.d.ts.map +1 -1
- package/dist/tools/export-spec.js +42 -1
- package/dist/tools/export-spec.js.map +1 -1
- package/dist/tools/generate-tests/generators/property-based-generator.d.ts +11 -0
- package/dist/tools/generate-tests/generators/property-based-generator.d.ts.map +1 -0
- package/dist/tools/generate-tests/generators/property-based-generator.js +27 -0
- package/dist/tools/generate-tests/generators/property-based-generator.js.map +1 -0
- package/dist/tools/generate-tests/spec-dispatcher.d.ts +5 -0
- package/dist/tools/generate-tests/spec-dispatcher.d.ts.map +1 -1
- package/dist/tools/generate-tests/spec-dispatcher.js +18 -1
- package/dist/tools/generate-tests/spec-dispatcher.js.map +1 -1
- package/dist/tools/init-project/config-builder.d.ts +3 -2
- package/dist/tools/init-project/config-builder.d.ts.map +1 -1
- package/dist/tools/init-project/config-builder.js +9 -4
- package/dist/tools/init-project/config-builder.js.map +1 -1
- package/dist/tools/init-project/handler.d.ts.map +1 -1
- package/dist/tools/init-project/handler.js +3 -2
- package/dist/tools/init-project/handler.js.map +1 -1
- package/dist/tools/jira-sync-handler.d.ts +6 -0
- package/dist/tools/jira-sync-handler.d.ts.map +1 -0
- package/dist/tools/jira-sync-handler.js +220 -0
- package/dist/tools/jira-sync-handler.js.map +1 -0
- package/dist/tools/linear-sync-handler.d.ts +6 -0
- package/dist/tools/linear-sync-handler.d.ts.map +1 -0
- package/dist/tools/linear-sync-handler.js +212 -0
- package/dist/tools/linear-sync-handler.js.map +1 -0
- package/dist/tools/register-export-tools.d.ts.map +1 -1
- package/dist/tools/register-export-tools.js +10 -3
- package/dist/tools/register-export-tools.js.map +1 -1
- package/dist/tools/register-spec-314.d.ts +3 -0
- package/dist/tools/register-spec-314.d.ts.map +1 -0
- package/dist/tools/register-spec-314.js +38 -0
- package/dist/tools/register-spec-314.js.map +1 -0
- package/dist/tools/register-spec-316.d.ts +3 -0
- package/dist/tools/register-spec-316.d.ts.map +1 -0
- package/dist/tools/register-spec-316.js +71 -0
- package/dist/tools/register-spec-316.js.map +1 -0
- package/dist/tools/register-spec-317.d.ts +3 -0
- package/dist/tools/register-spec-317.d.ts.map +1 -0
- package/dist/tools/register-spec-317.js +158 -0
- package/dist/tools/register-spec-317.js.map +1 -0
- package/dist/tools/register-spec-319.d.ts +3 -0
- package/dist/tools/register-spec-319.d.ts.map +1 -0
- package/dist/tools/register-spec-319.js +59 -0
- package/dist/tools/register-spec-319.js.map +1 -0
- package/dist/tools/register-spec-320.d.ts +3 -0
- package/dist/tools/register-spec-320.d.ts.map +1 -0
- package/dist/tools/register-spec-320.js +60 -0
- package/dist/tools/register-spec-320.js.map +1 -0
- package/dist/tools/register-spec-tools/core-spec-tools.d.ts.map +1 -1
- package/dist/tools/register-spec-tools/core-spec-tools.js +15 -1
- package/dist/tools/register-spec-tools/core-spec-tools.js.map +1 -1
- package/dist/tools/schemas/index.d.ts +1 -1
- package/dist/tools/schemas/index.d.ts.map +1 -1
- package/dist/tools/schemas/index.js +1 -1
- package/dist/tools/schemas/index.js.map +1 -1
- package/dist/tools/schemas/spec.d.ts +6 -0
- package/dist/tools/schemas/spec.d.ts.map +1 -1
- package/dist/tools/schemas/spec.js +4 -0
- package/dist/tools/schemas/spec.js.map +1 -1
- package/dist/tools/set-work-mode-handler.d.ts +7 -0
- package/dist/tools/set-work-mode-handler.d.ts.map +1 -0
- package/dist/tools/set-work-mode-handler.js +30 -0
- package/dist/tools/set-work-mode-handler.js.map +1 -0
- package/dist/tools/spec-marketplace-handler.d.ts +6 -0
- package/dist/tools/spec-marketplace-handler.d.ts.map +1 -0
- package/dist/tools/spec-marketplace-handler.js +113 -0
- package/dist/tools/spec-marketplace-handler.js.map +1 -0
- package/dist/tools/spec-quality-score-handler.d.ts +7 -0
- package/dist/tools/spec-quality-score-handler.d.ts.map +1 -0
- package/dist/tools/spec-quality-score-handler.js +106 -0
- package/dist/tools/spec-quality-score-handler.js.map +1 -0
- package/dist/tools/status-handler.d.ts.map +1 -1
- package/dist/tools/status-handler.js +12 -3
- package/dist/tools/status-handler.js.map +1 -1
- package/dist/types/agent-ready.d.ts +58 -0
- package/dist/types/agent-ready.d.ts.map +1 -0
- package/dist/types/agent-ready.js +3 -0
- package/dist/types/agent-ready.js.map +1 -0
- package/dist/types/common/primitives.d.ts +2 -0
- package/dist/types/common/primitives.d.ts.map +1 -1
- package/dist/types/compliance.d.ts +44 -0
- package/dist/types/compliance.d.ts.map +1 -0
- package/dist/types/compliance.js +3 -0
- package/dist/types/compliance.js.map +1 -0
- package/dist/types/export.d.ts +5 -1
- package/dist/types/export.d.ts.map +1 -1
- package/dist/types/export.js +1 -1
- package/dist/types/export.js.map +1 -1
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/jira-linear.d.ts +216 -0
- package/dist/types/jira-linear.d.ts.map +1 -0
- package/dist/types/jira-linear.js +3 -0
- package/dist/types/jira-linear.js.map +1 -0
- package/dist/types/project/inputs.d.ts +3 -1
- package/dist/types/project/inputs.d.ts.map +1 -1
- package/dist/types/project/planu-config.d.ts +2 -0
- package/dist/types/project/planu-config.d.ts.map +1 -1
- package/dist/types/property-testing.d.ts +50 -0
- package/dist/types/property-testing.d.ts.map +1 -0
- package/dist/types/property-testing.js +3 -0
- package/dist/types/property-testing.js.map +1 -0
- package/dist/types/skill-eval.d.ts +41 -0
- package/dist/types/skill-eval.d.ts.map +1 -0
- package/dist/types/skill-eval.js +3 -0
- package/dist/types/skill-eval.js.map +1 -0
- package/dist/types/spec-marketplace.d.ts +31 -0
- package/dist/types/spec-marketplace.d.ts.map +1 -0
- package/dist/types/spec-marketplace.js +3 -0
- package/dist/types/spec-marketplace.js.map +1 -0
- package/dist/types/spec-quality.d.ts +36 -0
- package/dist/types/spec-quality.d.ts.map +1 -0
- package/dist/types/spec-quality.js +3 -0
- package/dist/types/spec-quality.js.map +1 -0
- package/package.json +7 -2
- package/src/config/compliance-profiles.json +62 -0
- package/src/config/license-plans.json +19 -2
- package/src/config/spec-templates/crud-rest-api/spec.md +35 -0
- package/src/config/spec-templates/crud-rest-api/template.json +10 -0
- package/src/config/spec-templates/email-notifications/spec.md +31 -0
- package/src/config/spec-templates/email-notifications/template.json +10 -0
- package/src/config/spec-templates/file-upload-s3/spec.md +31 -0
- package/src/config/spec-templates/file-upload-s3/template.json +11 -0
- package/src/config/spec-templates/jwt-auth/spec.md +35 -0
- package/src/config/spec-templates/jwt-auth/template.json +10 -0
- package/src/config/spec-templates/oauth-social-login/spec.md +31 -0
- package/src/config/spec-templates/oauth-social-login/template.json +10 -0
- package/src/config/spec-templates/rate-limiting/spec.md +31 -0
- package/src/config/spec-templates/rate-limiting/template.json +10 -0
- package/src/config/spec-templates/stripe-payments/spec.md +32 -0
- package/src/config/spec-templates/stripe-payments/template.json +10 -0
- package/src/config/spec-templates/webhook-system/spec.md +36 -0
- package/src/config/spec-templates/webhook-system/template.json +10 -0
|
@@ -2,8 +2,9 @@ const PLANU_CONFIG_VERSION = '1.0.0';
|
|
|
2
2
|
const DEFAULT_SPEC_LOCATION = 'planu/specs';
|
|
3
3
|
/**
|
|
4
4
|
* Create a new PlanuConfig with defaults. Always uses modern spec location.
|
|
5
|
+
* SPEC-313: Accepts optional workMode to embed in the generated config.
|
|
5
6
|
*/
|
|
6
|
-
export function createPlanuConfig() {
|
|
7
|
+
export function createPlanuConfig(workMode) {
|
|
7
8
|
const now = new Date().toISOString();
|
|
8
9
|
return {
|
|
9
10
|
version: PLANU_CONFIG_VERSION,
|
|
@@ -11,8 +12,38 @@ export function createPlanuConfig() {
|
|
|
11
12
|
specNaming: 'modern',
|
|
12
13
|
createdAt: now,
|
|
13
14
|
updatedAt: now,
|
|
15
|
+
...(workMode !== undefined ? { workMode } : {}),
|
|
14
16
|
};
|
|
15
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* SPEC-313: Read planu.json, update workMode, and write back.
|
|
20
|
+
* Accepts injected readFile/writeFile to keep this function pure/testable.
|
|
21
|
+
*/
|
|
22
|
+
export async function updatePlanuConfigWorkMode(planuConfigPath, workMode, readFile, writeFile) {
|
|
23
|
+
try {
|
|
24
|
+
const raw = await readFile(planuConfigPath, 'utf-8');
|
|
25
|
+
const config = JSON.parse(raw);
|
|
26
|
+
config.workMode = workMode;
|
|
27
|
+
config.updatedAt = new Date().toISOString();
|
|
28
|
+
await writeFile(planuConfigPath, serializePlanuConfig(config), 'utf-8');
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// planu.json absent or invalid — skip silently
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* SPEC-313: Read and parse planu.json to retrieve its config (best-effort).
|
|
36
|
+
* Returns null if the file is absent or malformed.
|
|
37
|
+
*/
|
|
38
|
+
export async function readPlanuConfig(planuConfigPath, readFile) {
|
|
39
|
+
try {
|
|
40
|
+
const raw = await readFile(planuConfigPath, 'utf-8');
|
|
41
|
+
return JSON.parse(raw);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
16
47
|
/**
|
|
17
48
|
* Serialize PlanuConfig to JSON string.
|
|
18
49
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planu-config-writer.js","sourceRoot":"","sources":["../../src/engine/planu-config-writer.ts"],"names":[],"mappings":"AAGA,MAAM,oBAAoB,GAAG,OAAO,CAAC;AACrC,MAAM,qBAAqB,GAAG,aAAa,CAAC;AAE5C
|
|
1
|
+
{"version":3,"file":"planu-config-writer.js","sourceRoot":"","sources":["../../src/engine/planu-config-writer.ts"],"names":[],"mappings":"AAGA,MAAM,oBAAoB,GAAG,OAAO,CAAC;AACrC,MAAM,qBAAqB,GAAG,aAAa,CAAC;AAE5C;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAmB;IACnD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,OAAO;QACL,OAAO,EAAE,oBAAoB;QAC7B,YAAY,EAAE,qBAAqB;QACnC,UAAU,EAAE,QAAQ;QACpB,SAAS,EAAE,GAAG;QACd,SAAS,EAAE,GAAG;QACd,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChD,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,eAAuB,EACvB,QAAkB,EAClB,QAAyD,EACzD,SAAsE;IAEtE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAC;QAC9C,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC3B,MAAM,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC5C,MAAM,SAAS,CAAC,eAAe,EAAE,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,eAAuB,EACvB,QAAyD;IAEzD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAmB;IACtD,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACzC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAA2B;IAK1D,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;IACnF,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;AACjF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,YAAoB;IACnD,OAAO,YAAY,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PropertyTest, PropertyTestExtractionResult, PropertyTestFrameworkTarget, PropertyBasedGeneratorInput } from '../types/property-testing.js';
|
|
2
|
+
/** Map a language/stack to the best property-based test framework. */
|
|
3
|
+
export declare function detectPropertyFramework(language: string, stack?: string[]): PropertyTestFrameworkTarget;
|
|
4
|
+
/**
|
|
5
|
+
* Extract PropertyTest objects from acceptance criteria strings.
|
|
6
|
+
* Each criterion is analyzed against known invariant patterns.
|
|
7
|
+
* Criteria with no detected pattern are skipped.
|
|
8
|
+
*/
|
|
9
|
+
export declare function extractPropertyTests(input: PropertyBasedGeneratorInput): PropertyTest[];
|
|
10
|
+
/**
|
|
11
|
+
* Full extraction with metadata: detected framework and analyzed criteria list.
|
|
12
|
+
*/
|
|
13
|
+
export declare function extractPropertyTestsWithMeta(input: PropertyBasedGeneratorInput): PropertyTestExtractionResult;
|
|
14
|
+
//# sourceMappingURL=property-test-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"property-test-generator.d.ts","sourceRoot":"","sources":["../../src/engine/property-test-generator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,YAAY,EACZ,4BAA4B,EAC5B,2BAA2B,EAC3B,2BAA2B,EAE5B,MAAM,8BAA8B,CAAC;AAMtC,sEAAsE;AACtE,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAM,EAAO,GACnB,2BAA2B,CAyB7B;AA0KD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,2BAA2B,GAAG,YAAY,EAAE,CAuBvF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,2BAA2B,GACjC,4BAA4B,CAS9B"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// engine/property-test-generator.ts — SPEC-315
|
|
2
|
+
// Extracts invariants from acceptance criteria and generates PropertyTest objects.
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Framework detection
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
/** Map a language/stack to the best property-based test framework. */
|
|
7
|
+
export function detectPropertyFramework(language, stack = []) {
|
|
8
|
+
const lang = language.toLowerCase();
|
|
9
|
+
const stackLower = stack.map((s) => s.toLowerCase());
|
|
10
|
+
if (stackLower.includes('fast-check') || stackLower.includes('fastcheck')) {
|
|
11
|
+
return 'fast-check';
|
|
12
|
+
}
|
|
13
|
+
if (stackLower.includes('hypothesis')) {
|
|
14
|
+
return 'hypothesis';
|
|
15
|
+
}
|
|
16
|
+
if (stackLower.includes('jqwik')) {
|
|
17
|
+
return 'jqwik';
|
|
18
|
+
}
|
|
19
|
+
if (lang === 'typescript' || lang === 'javascript') {
|
|
20
|
+
return 'fast-check';
|
|
21
|
+
}
|
|
22
|
+
if (lang === 'python') {
|
|
23
|
+
return 'hypothesis';
|
|
24
|
+
}
|
|
25
|
+
if (lang === 'java' || lang === 'kotlin') {
|
|
26
|
+
return 'jqwik';
|
|
27
|
+
}
|
|
28
|
+
return 'generic';
|
|
29
|
+
}
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Invariant pattern matchers
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
const INVARIANT_PATTERNS = [
|
|
34
|
+
{
|
|
35
|
+
// "price must be positive" / "amount should be positive" / "value must be >= 0"
|
|
36
|
+
pattern: /(?:price|amount|value|cost|total|balance)\s+(?:must|should)\s+be\s+(?:positive|>?\s*0|greater than 0)/i,
|
|
37
|
+
extract: (_m, raw) => ({
|
|
38
|
+
invariant: `${raw.trim()} — output is always > 0`,
|
|
39
|
+
inputDomain: 'positive floats (min: 0.01)',
|
|
40
|
+
}),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
// "must never be negative" / "should never be negative"
|
|
44
|
+
pattern: /(?:must|should)\s+never\s+be\s+negative/i,
|
|
45
|
+
extract: (_m, raw) => ({
|
|
46
|
+
invariant: `${raw.trim()} — result is never negative`,
|
|
47
|
+
inputDomain: 'non-negative floats (min: 0)',
|
|
48
|
+
}),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
// "idempotent" / "idempotente"
|
|
52
|
+
pattern: /idempoten(?:t|te)/i,
|
|
53
|
+
extract: (_m, raw) => ({
|
|
54
|
+
invariant: `${raw.trim()} — applying N times equals applying once`,
|
|
55
|
+
inputDomain: 'arbitrary valid inputs',
|
|
56
|
+
}),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
// "commutative" / "conmutativo"
|
|
60
|
+
pattern: /commutativ(?:e|ity)|conmutativ(?:o|idad)/i,
|
|
61
|
+
extract: (_m, raw) => ({
|
|
62
|
+
invariant: `${raw.trim()} — f(a,b) === f(b,a)`,
|
|
63
|
+
inputDomain: 'pairs of arbitrary values',
|
|
64
|
+
}),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
// "always returns / always produces"
|
|
68
|
+
pattern: /always\s+(?:returns?|produces?|yields?)\s+(.+)/i,
|
|
69
|
+
extract: (m, raw) => ({
|
|
70
|
+
invariant: `${raw.trim()} — output is always ${m[1] ?? 'defined'}`,
|
|
71
|
+
inputDomain: 'arbitrary valid inputs',
|
|
72
|
+
}),
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
// "must not be empty" / "should not be empty"
|
|
76
|
+
pattern: /(?:must|should)\s+not\s+be\s+empty/i,
|
|
77
|
+
extract: (_m, raw) => ({
|
|
78
|
+
invariant: `${raw.trim()} — result is never empty`,
|
|
79
|
+
inputDomain: 'non-empty strings or arrays',
|
|
80
|
+
}),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
// "for any input" / "for all inputs" / "para cualquier" / "para todos"
|
|
84
|
+
pattern: /(?:for\s+(?:any|all)\s+(?:valid\s+)?input|para\s+(?:cualquier|todos))/i,
|
|
85
|
+
extract: (_m, raw) => ({
|
|
86
|
+
invariant: `${raw.trim()} — holds for all valid inputs`,
|
|
87
|
+
inputDomain: 'arbitrary valid inputs',
|
|
88
|
+
}),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
// "length must be between X and Y"
|
|
92
|
+
pattern: /length\s+must\s+be\s+(?:between\s+)?(\d+)\s+and\s+(\d+)/i,
|
|
93
|
+
extract: (m, raw) => ({
|
|
94
|
+
invariant: `${raw.trim()} — length is within [${m[1] ?? '0'}, ${m[2] ?? '∞'}]`,
|
|
95
|
+
inputDomain: `strings of length ${m[1] ?? '0'}..${m[2] ?? '100'}`,
|
|
96
|
+
}),
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
// "must be unique" / "should be unique"
|
|
100
|
+
pattern: /(?:must|should)\s+be\s+unique/i,
|
|
101
|
+
extract: (_m, raw) => ({
|
|
102
|
+
invariant: `${raw.trim()} — no duplicates in output`,
|
|
103
|
+
inputDomain: 'arrays of arbitrary comparable values',
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
// "must be sorted" / "should be sorted" / "maintains order"
|
|
108
|
+
pattern: /(?:must|should)\s+be\s+sorted|maintains?\s+(?:sort\s+)?order/i,
|
|
109
|
+
extract: (_m, raw) => ({
|
|
110
|
+
invariant: `${raw.trim()} — output is sorted`,
|
|
111
|
+
inputDomain: 'arrays of comparable values',
|
|
112
|
+
}),
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Code generators per framework
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
function buildFastCheckCode(invariant, inputDomain) {
|
|
119
|
+
return `fc.assert(
|
|
120
|
+
fc.property(
|
|
121
|
+
fc.float({ min: 0.01, noNaN: true }), // ${inputDomain}
|
|
122
|
+
(input) => {
|
|
123
|
+
const result = functionUnderTest(input);
|
|
124
|
+
// ${invariant}
|
|
125
|
+
return result !== null && result !== undefined;
|
|
126
|
+
},
|
|
127
|
+
),
|
|
128
|
+
);`;
|
|
129
|
+
}
|
|
130
|
+
function buildHypothesisCode(invariant, inputDomain) {
|
|
131
|
+
const safeName = invariant.toLowerCase().replace(/[^a-z0-9]/g, '_').slice(0, 40);
|
|
132
|
+
return `@given(st.floats(min_value=0.01, allow_nan=False)) # ${inputDomain}
|
|
133
|
+
@settings(max_examples=100, deadline=None)
|
|
134
|
+
def test_${safeName}(input_value: float) -> None:
|
|
135
|
+
"""${invariant}"""
|
|
136
|
+
result = function_under_test(input_value)
|
|
137
|
+
assert result is not None`;
|
|
138
|
+
}
|
|
139
|
+
function buildJqwikCode(invariant, inputDomain) {
|
|
140
|
+
const safeName = invariant.replace(/[^a-zA-Z0-9]/g, '').slice(0, 40);
|
|
141
|
+
return `@Property
|
|
142
|
+
boolean ${safeName}(@ForAll @Positive double input) {
|
|
143
|
+
// ${inputDomain}
|
|
144
|
+
// ${invariant}
|
|
145
|
+
Object result = functionUnderTest(input);
|
|
146
|
+
return result != null;
|
|
147
|
+
}`;
|
|
148
|
+
}
|
|
149
|
+
function buildGenericCode(invariant, inputDomain) {
|
|
150
|
+
return `// Property: ${invariant}
|
|
151
|
+
// Input domain: ${inputDomain}
|
|
152
|
+
// For each arbitrary valid input in the domain:
|
|
153
|
+
// 1. Generate input using your framework's generator
|
|
154
|
+
// 2. Call the function under test
|
|
155
|
+
// 3. Assert: ${invariant}`;
|
|
156
|
+
}
|
|
157
|
+
function buildGeneratorCode(framework, invariant, inputDomain) {
|
|
158
|
+
switch (framework) {
|
|
159
|
+
case 'fast-check':
|
|
160
|
+
return buildFastCheckCode(invariant, inputDomain);
|
|
161
|
+
case 'hypothesis':
|
|
162
|
+
return buildHypothesisCode(invariant, inputDomain);
|
|
163
|
+
case 'jqwik':
|
|
164
|
+
return buildJqwikCode(invariant, inputDomain);
|
|
165
|
+
case 'generic':
|
|
166
|
+
return buildGenericCode(invariant, inputDomain);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function getShrinkStrategy(framework) {
|
|
170
|
+
switch (framework) {
|
|
171
|
+
case 'fast-check':
|
|
172
|
+
return 'automatic (built-in shrinker reduces to minimal failing case)';
|
|
173
|
+
case 'hypothesis':
|
|
174
|
+
return 'automatic (Hypothesis shrinks via @settings(deriving=True))';
|
|
175
|
+
case 'jqwik':
|
|
176
|
+
return 'automatic (jqwik shrinks @ForAll parameters)';
|
|
177
|
+
case 'generic':
|
|
178
|
+
return 'manual (implement shrink by binary search or domain reduction)';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// Public API
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
/**
|
|
185
|
+
* Extract PropertyTest objects from acceptance criteria strings.
|
|
186
|
+
* Each criterion is analyzed against known invariant patterns.
|
|
187
|
+
* Criteria with no detected pattern are skipped.
|
|
188
|
+
*/
|
|
189
|
+
export function extractPropertyTests(input) {
|
|
190
|
+
const framework = detectPropertyFramework(input.language, input.stack);
|
|
191
|
+
const shrinkStrategy = getShrinkStrategy(framework);
|
|
192
|
+
const results = [];
|
|
193
|
+
for (const criterion of input.criteria) {
|
|
194
|
+
for (const { pattern, extract } of INVARIANT_PATTERNS) {
|
|
195
|
+
const match = criterion.match(pattern);
|
|
196
|
+
if (match) {
|
|
197
|
+
const { invariant, inputDomain } = extract(match, criterion);
|
|
198
|
+
results.push({
|
|
199
|
+
invariant,
|
|
200
|
+
inputDomain,
|
|
201
|
+
generatorCode: buildGeneratorCode(framework, invariant, inputDomain),
|
|
202
|
+
framework,
|
|
203
|
+
shrinkStrategy,
|
|
204
|
+
});
|
|
205
|
+
break; // one pattern per criterion
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return results;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Full extraction with metadata: detected framework and analyzed criteria list.
|
|
213
|
+
*/
|
|
214
|
+
export function extractPropertyTestsWithMeta(input) {
|
|
215
|
+
const detectedFramework = detectPropertyFramework(input.language, input.stack);
|
|
216
|
+
const tests = extractPropertyTests(input);
|
|
217
|
+
return {
|
|
218
|
+
tests,
|
|
219
|
+
detectedFramework,
|
|
220
|
+
analyzedCriteria: input.criteria,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=property-test-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"property-test-generator.js","sourceRoot":"","sources":["../../src/engine/property-test-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { EvalScenario, SkillEvalResult } from '../types/skill-eval.js';
|
|
2
|
+
/**
|
|
3
|
+
* Evaluate a skill file at the given path against the provided scenarios.
|
|
4
|
+
* Reads .claude/skills/{skillName}.md from the skill path.
|
|
5
|
+
*/
|
|
6
|
+
export declare function evaluateSkill(skillPath: string, scenarios: EvalScenario[]): Promise<SkillEvalResult>;
|
|
7
|
+
/**
|
|
8
|
+
* Evaluate a rule file at the given path against the provided scenarios.
|
|
9
|
+
* Reads .claude/rules/{ruleName}.md from the rule path.
|
|
10
|
+
*/
|
|
11
|
+
export declare function evaluateRule(rulePath: string, scenarios: EvalScenario[]): Promise<SkillEvalResult>;
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the absolute path to a skill file under a project root.
|
|
14
|
+
* Returns the resolved path (may not exist yet — caller decides how to handle).
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveSkillPath(projectPath: string, skillName: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the absolute path to a rule file under a project root.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveRulePath(projectPath: string, ruleName: string): string;
|
|
21
|
+
//# sourceMappingURL=skill-evaluator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-evaluator.d.ts","sourceRoot":"","sources":["../../src/engine/skill-evaluator.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAkB,eAAe,EAAE,MAAM,wBAAwB,CAAC;AA8H5F;;;GAGG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,YAAY,EAAE,GACxB,OAAO,CAAC,eAAe,CAAC,CAG1B;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,YAAY,EAAE,GACxB,OAAO,CAAC,eAAe,CAAC,CAG1B;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG/E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG7E"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// engine/skill-evaluator.ts — Evaluates skill and rule quality against test scenarios (SPEC-316)
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Internal helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
/**
|
|
9
|
+
* Read a file from disk, returning empty string if it does not exist.
|
|
10
|
+
*/
|
|
11
|
+
async function readContent(filePath) {
|
|
12
|
+
if (!existsSync(filePath)) {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
return readFile(filePath, 'utf-8');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Evaluate a single scenario against the given content.
|
|
19
|
+
* A criterion passes if it appears (case-insensitive) somewhere in the content
|
|
20
|
+
* OR in the expectedOutput section of the content.
|
|
21
|
+
*/
|
|
22
|
+
function evaluateScenario(content, scenario) {
|
|
23
|
+
const lower = content.toLowerCase();
|
|
24
|
+
const expectedLower = scenario.expectedOutput.toLowerCase();
|
|
25
|
+
const failures = [];
|
|
26
|
+
for (const criterion of scenario.criteria) {
|
|
27
|
+
const criterionLower = criterion.toLowerCase();
|
|
28
|
+
const inContent = lower.includes(criterionLower);
|
|
29
|
+
const inExpected = expectedLower.includes(criterionLower);
|
|
30
|
+
if (!inContent && !inExpected) {
|
|
31
|
+
failures.push(criterion);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const total = scenario.criteria.length;
|
|
35
|
+
const passed = total - failures.length;
|
|
36
|
+
const score = total === 0 ? 1.0 : passed / total;
|
|
37
|
+
return {
|
|
38
|
+
input: scenario.input,
|
|
39
|
+
passed: failures.length === 0,
|
|
40
|
+
score,
|
|
41
|
+
failures,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build recommendations based on scenario results.
|
|
46
|
+
*/
|
|
47
|
+
function buildRecommendations(skillName, results, passRate) {
|
|
48
|
+
const recommendations = [];
|
|
49
|
+
if (passRate < 0.5) {
|
|
50
|
+
recommendations.push(`${skillName} covers fewer than half of tested scenarios — consider a major revision.`);
|
|
51
|
+
}
|
|
52
|
+
else if (passRate < 0.8) {
|
|
53
|
+
recommendations.push(`${skillName} passes ${Math.round(passRate * 100)}% of scenarios — review failing criteria.`);
|
|
54
|
+
}
|
|
55
|
+
// Collect the most common missing criteria across all failures
|
|
56
|
+
const failureCounts = new Map();
|
|
57
|
+
for (const r of results) {
|
|
58
|
+
for (const f of r.failures) {
|
|
59
|
+
failureCounts.set(f, (failureCounts.get(f) ?? 0) + 1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const sorted = [...failureCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
63
|
+
const top = sorted.slice(0, 3);
|
|
64
|
+
for (const [criterion, count] of top) {
|
|
65
|
+
recommendations.push(`Add coverage for "${criterion}" — missing in ${String(count)} scenario(s).`);
|
|
66
|
+
}
|
|
67
|
+
if (recommendations.length === 0) {
|
|
68
|
+
recommendations.push(`${skillName} meets all evaluated criteria — no changes needed.`);
|
|
69
|
+
}
|
|
70
|
+
return recommendations;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Core evaluation logic used by both evaluateSkill and evaluateRule.
|
|
74
|
+
*/
|
|
75
|
+
async function evaluate(filePath, displayName, scenarios) {
|
|
76
|
+
const content = await readContent(filePath);
|
|
77
|
+
const scenarioResults = scenarios.map((s) => evaluateScenario(content, s));
|
|
78
|
+
const passedCount = scenarioResults.filter((r) => r.passed).length;
|
|
79
|
+
const passRate = scenarios.length === 0 ? 1.0 : passedCount / scenarios.length;
|
|
80
|
+
const avgScore = scenarioResults.length === 0
|
|
81
|
+
? 1.0
|
|
82
|
+
: scenarioResults.reduce((sum, r) => sum + r.score, 0) / scenarioResults.length;
|
|
83
|
+
const recommendations = buildRecommendations(displayName, scenarioResults, passRate);
|
|
84
|
+
return {
|
|
85
|
+
skillName: displayName,
|
|
86
|
+
passRate,
|
|
87
|
+
avgScore,
|
|
88
|
+
scenarios: scenarioResults,
|
|
89
|
+
recommendations,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Public API
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
/**
|
|
96
|
+
* Evaluate a skill file at the given path against the provided scenarios.
|
|
97
|
+
* Reads .claude/skills/{skillName}.md from the skill path.
|
|
98
|
+
*/
|
|
99
|
+
export async function evaluateSkill(skillPath, scenarios) {
|
|
100
|
+
const name = skillPath.split('/').pop() ?? skillPath;
|
|
101
|
+
return evaluate(skillPath, name.replace(/\.md$/i, ''), scenarios);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Evaluate a rule file at the given path against the provided scenarios.
|
|
105
|
+
* Reads .claude/rules/{ruleName}.md from the rule path.
|
|
106
|
+
*/
|
|
107
|
+
export async function evaluateRule(rulePath, scenarios) {
|
|
108
|
+
const name = rulePath.split('/').pop() ?? rulePath;
|
|
109
|
+
return evaluate(rulePath, name.replace(/\.md$/i, ''), scenarios);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Resolve the absolute path to a skill file under a project root.
|
|
113
|
+
* Returns the resolved path (may not exist yet — caller decides how to handle).
|
|
114
|
+
*/
|
|
115
|
+
export function resolveSkillPath(projectPath, skillName) {
|
|
116
|
+
const normalized = skillName.endsWith('.md') ? skillName : `${skillName}.md`;
|
|
117
|
+
return join(projectPath, '.claude', 'skills', normalized);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Resolve the absolute path to a rule file under a project root.
|
|
121
|
+
*/
|
|
122
|
+
export function resolveRulePath(projectPath, ruleName) {
|
|
123
|
+
const normalized = ruleName.endsWith('.md') ? ruleName : `${ruleName}.md`;
|
|
124
|
+
return join(projectPath, '.claude', 'rules', normalized);
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=skill-evaluator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-evaluator.js","sourceRoot":"","sources":["../../src/engine/skill-evaluator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-quality-scorer.d.ts","sourceRoot":"","sources":["../../src/engine/spec-quality-scorer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAIV,iBAAiB,EAClB,MAAM,0BAA0B,CAAC;AA8UlC,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAmC7E"}
|