@popoverai/dotrequirements 0.23.0 → 0.24.1
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/README.md +169 -22
- package/dist/cli.js +121 -60
- package/dist/codebase-to-spec/budget.d.ts +53 -0
- package/dist/codebase-to-spec/budget.js +80 -0
- package/dist/codebase-to-spec/cache.d.ts +49 -0
- package/dist/codebase-to-spec/cache.js +54 -0
- package/dist/codebase-to-spec/claude.d.ts +69 -0
- package/dist/codebase-to-spec/claude.js +126 -0
- package/dist/codebase-to-spec/compose.d.ts +49 -0
- package/dist/codebase-to-spec/compose.js +124 -0
- package/dist/codebase-to-spec/edit-loop.d.ts +54 -0
- package/dist/codebase-to-spec/edit-loop.js +195 -0
- package/dist/codebase-to-spec/editor.d.ts +54 -0
- package/dist/codebase-to-spec/editor.js +74 -0
- package/dist/codebase-to-spec/exit-codes.d.ts +40 -0
- package/dist/codebase-to-spec/exit-codes.js +58 -0
- package/dist/codebase-to-spec/fan-out.d.ts +63 -0
- package/dist/codebase-to-spec/fan-out.js +215 -0
- package/dist/codebase-to-spec/interactive.d.ts +30 -0
- package/dist/codebase-to-spec/interactive.js +48 -0
- package/dist/codebase-to-spec/outline-review-loop.d.ts +51 -0
- package/dist/codebase-to-spec/outline-review-loop.js +187 -0
- package/dist/codebase-to-spec/pack.d.ts +51 -0
- package/dist/codebase-to-spec/pack.js +127 -0
- package/dist/codebase-to-spec/planner.d.ts +41 -0
- package/dist/codebase-to-spec/planner.js +76 -0
- package/dist/codebase-to-spec/present.d.ts +94 -0
- package/dist/codebase-to-spec/present.js +288 -0
- package/dist/codebase-to-spec/progress.d.ts +33 -0
- package/dist/codebase-to-spec/progress.js +28 -0
- package/dist/codebase-to-spec/prompts/editor.d.ts +13 -0
- package/dist/codebase-to-spec/prompts/editor.js +57 -0
- package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/outline-reviewer.js +87 -0
- package/dist/codebase-to-spec/prompts/planner-initial.d.ts +11 -0
- package/dist/codebase-to-spec/prompts/planner-initial.js +125 -0
- package/dist/codebase-to-spec/prompts/planner-revise.d.ts +14 -0
- package/dist/codebase-to-spec/prompts/planner-revise.js +60 -0
- package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +16 -0
- package/dist/codebase-to-spec/prompts/spec-reviewer.js +96 -0
- package/dist/codebase-to-spec/prompts/specifier.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/specifier.js +100 -0
- package/dist/codebase-to-spec/prompts/style-check.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/style-check.js +78 -0
- package/dist/codebase-to-spec/schemas.d.ts +257 -0
- package/dist/codebase-to-spec/schemas.js +183 -0
- package/dist/codebase-to-spec/skill-install.d.ts +57 -0
- package/dist/codebase-to-spec/skill-install.js +79 -0
- package/dist/codebase-to-spec/slice.d.ts +49 -0
- package/dist/codebase-to-spec/slice.js +111 -0
- package/dist/codebase-to-spec/specifier.d.ts +60 -0
- package/dist/codebase-to-spec/specifier.js +79 -0
- package/dist/codebase-to-spec/style-check.d.ts +29 -0
- package/dist/codebase-to-spec/style-check.js +33 -0
- package/dist/codebase-to-spec/summary.d.ts +51 -0
- package/dist/codebase-to-spec/summary.js +183 -0
- package/dist/codebase-to-spec/validate.d.ts +46 -0
- package/dist/codebase-to-spec/validate.js +130 -0
- package/dist/commands/acceptance-test.d.ts +6 -0
- package/dist/commands/{browsertest.js → acceptance-test.js} +36 -29
- package/dist/commands/ai-setup.d.ts +5 -0
- package/dist/commands/ai-setup.js +441 -0
- package/dist/commands/codebase-to-spec/compose.d.ts +14 -0
- package/dist/commands/codebase-to-spec/compose.js +57 -0
- package/dist/commands/codebase-to-spec/edit-loop.d.ts +16 -0
- package/dist/commands/codebase-to-spec/edit-loop.js +83 -0
- package/dist/commands/codebase-to-spec/fan-out.d.ts +19 -0
- package/dist/commands/codebase-to-spec/fan-out.js +77 -0
- package/dist/commands/codebase-to-spec/index.d.ts +9 -0
- package/dist/commands/codebase-to-spec/index.js +135 -0
- package/dist/commands/codebase-to-spec/pack.d.ts +22 -0
- package/dist/commands/codebase-to-spec/pack.js +76 -0
- package/dist/commands/codebase-to-spec/plan-loop.d.ts +26 -0
- package/dist/commands/codebase-to-spec/plan-loop.js +105 -0
- package/dist/commands/codebase-to-spec/present.d.ts +21 -0
- package/dist/commands/codebase-to-spec/present.js +92 -0
- package/dist/commands/codebase-to-spec/run.d.ts +20 -0
- package/dist/commands/codebase-to-spec/run.js +85 -0
- package/dist/commands/codebase-to-spec/skill-install.d.ts +20 -0
- package/dist/commands/codebase-to-spec/skill-install.js +51 -0
- package/dist/commands/codebase-to-spec/specify-area.d.ts +18 -0
- package/dist/commands/codebase-to-spec/specify-area.js +82 -0
- package/dist/commands/codebase-to-spec/style-check.d.ts +15 -0
- package/dist/commands/codebase-to-spec/style-check.js +42 -0
- package/dist/commands/codebase-to-spec/validate.d.ts +18 -0
- package/dist/commands/codebase-to-spec/validate.js +38 -0
- package/dist/commands/create-requirement-document.d.ts +2 -0
- package/dist/commands/create-requirement-document.js +41 -0
- package/dist/commands/finalize.js +7 -7
- package/dist/commands/get.d.ts +2 -0
- package/dist/commands/get.js +55 -0
- package/dist/commands/init.js +132 -117
- package/dist/commands/link.js +27 -27
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.js +43 -0
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/prepare.js +4 -4
- package/dist/commands/pull.js +116 -121
- package/dist/commands/push.js +106 -112
- package/dist/commands/report.d.ts +6 -2
- package/dist/commands/report.js +177 -122
- package/dist/commands/requirements-for.d.ts +2 -0
- package/dist/commands/requirements-for.js +29 -0
- package/dist/commands/review-test.d.ts +2 -0
- package/dist/commands/review-test.js +75 -0
- package/dist/commands/search.d.ts +6 -0
- package/dist/commands/search.js +39 -0
- package/dist/commands/style-check.d.ts +7 -0
- package/dist/commands/style-check.js +75 -0
- package/dist/commands/tests-for.d.ts +2 -0
- package/dist/commands/tests-for.js +80 -0
- package/dist/commands/validate.d.ts +6 -0
- package/dist/commands/validate.js +72 -0
- package/dist/config.js +1 -1
- package/dist/convex.d.ts +34 -22
- package/dist/convex.js +38 -22
- package/dist/harness/cache.d.ts +1 -5
- package/dist/harness/cache.js +49 -59
- package/dist/harness/convexReporting.d.ts +1 -1
- package/dist/harness/convexReporting.js +9 -7
- package/dist/harness/coverageCache.js +3 -3
- package/dist/harness/finalize.js +59 -46
- package/dist/harness/index.d.ts +6 -7
- package/dist/harness/index.js +9 -10
- package/dist/harness/prepare.js +6 -5
- package/dist/harness/requirementsLoader.d.ts +2 -2
- package/dist/harness/requirementsLoader.js +13 -35
- package/dist/harness/tracking.js +18 -18
- package/dist/harness/types.d.ts +1 -1
- package/dist/mcp/convexClient.d.ts +0 -39
- package/dist/mcp/convexClient.js +2 -107
- package/dist/mcp/handlers/authoring.d.ts +1 -1
- package/dist/mcp/handlers/authoring.js +30 -234
- package/dist/mcp/handlers/debug.d.ts +2 -3
- package/dist/mcp/handlers/debug.js +10 -10
- package/dist/mcp/handlers/get.d.ts +1 -1
- package/dist/mcp/handlers/get.js +11 -10
- package/dist/mcp/handlers/index.d.ts +20 -20
- package/dist/mcp/handlers/index.js +10 -10
- package/dist/mcp/handlers/list.d.ts +4 -33
- package/dist/mcp/handlers/list.js +16 -38
- package/dist/mcp/handlers/push.d.ts +1 -1
- package/dist/mcp/handlers/push.js +28 -18
- package/dist/mcp/handlers/report.d.ts +16 -0
- package/dist/mcp/handlers/report.js +134 -0
- package/dist/mcp/handlers/review.d.ts +1 -1
- package/dist/mcp/handlers/review.js +40 -59
- package/dist/mcp/handlers/search.d.ts +1 -1
- package/dist/mcp/handlers/search.js +7 -9
- package/dist/mcp/handlers/test-mapping.d.ts +1 -1
- package/dist/mcp/handlers/test-mapping.js +14 -14
- package/dist/mcp/handlers/types.d.ts +3 -3
- package/dist/mcp/handlers/types.js +2 -2
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +147 -167
- package/dist/push/core.d.ts +2 -2
- package/dist/push/core.js +20 -20
- package/dist/push/index.d.ts +1 -1
- package/dist/push/index.js +2 -2
- package/dist/requirements/cloud-ai.d.ts +57 -0
- package/dist/requirements/cloud-ai.js +104 -0
- package/dist/requirements/cloud-coverage.d.ts +41 -0
- package/dist/requirements/cloud-coverage.js +60 -0
- package/dist/requirements/coverage.d.ts +45 -0
- package/dist/requirements/coverage.js +114 -0
- package/dist/{mcp → requirements}/grep.d.ts +10 -1
- package/dist/{mcp → requirements}/grep.js +89 -44
- package/dist/{mcp/requirements.d.ts → requirements/index.d.ts} +19 -3
- package/dist/{mcp/requirements.js → requirements/index.js} +54 -35
- package/dist/requirements/style-guide.d.ts +67 -0
- package/dist/requirements/style-guide.js +299 -0
- package/dist/{mcp → requirements}/testCodeExtractor.js +24 -26
- package/dist/schema/browser.d.ts +8 -8
- package/dist/schema/browser.js +13 -15
- package/dist/schema/builder.d.ts +1 -1
- package/dist/schema/builder.js +13 -44
- package/dist/schema/conversions.d.ts +2 -2
- package/dist/schema/conversions.js +11 -11
- package/dist/schema/index.d.ts +9 -9
- package/dist/schema/index.js +15 -15
- package/dist/schema/parser-core.d.ts +1 -1
- package/dist/schema/parser-core.js +23 -22
- package/dist/schema/parser.d.ts +3 -3
- package/dist/schema/parser.js +27 -31
- package/dist/schema/resolver.d.ts +1 -1
- package/dist/schema/resolver.js +9 -9
- package/dist/schema/scenario.d.ts +1 -1
- package/dist/schema/scenario.js +1 -1
- package/dist/schema/schemas.d.ts +3 -3
- package/dist/schema/schemas.js +41 -28
- package/dist/schema/test-schema.js +27 -27
- package/dist/templates/context-file-section.md +3 -2
- package/dist/templates/example-requirements.js +1 -1
- package/dist/templates/example-requirements.ts +3 -1
- package/dist/templates/requirements-readme.js +1 -1
- package/dist/templates/requirements-readme.ts +1 -1
- package/dist/templates/skills/codebase-to-spec/SKILL.md +118 -0
- package/dist/utils/brand.js +3 -3
- package/dist/utils/browser-launch.js +4 -4
- package/dist/utils/context-file.d.ts +1 -1
- package/dist/utils/context-file.js +26 -26
- package/dist/utils/env.js +7 -7
- package/dist/utils/gitignore.js +7 -7
- package/dist/utils/oauth-callback-server.d.ts +1 -1
- package/dist/utils/oauth-callback-server.js +27 -25
- package/dist/utils/oauth-flow.js +32 -29
- package/dist/utils/project-discovery.d.ts +3 -3
- package/dist/utils/project-discovery.js +18 -17
- package/dist/utils/project-name.js +8 -8
- package/dist/utils/project-selector.d.ts +1 -1
- package/dist/utils/project-selector.js +24 -21
- package/dist/utils/project-settings.d.ts +1 -1
- package/dist/utils/project-settings.js +24 -22
- package/dist/utils/templates.js +6 -6
- package/package.json +3 -2
- package/dist/commands/browsertest.d.ts +0 -6
- package/dist/commands/login.d.ts +0 -12
- package/dist/commands/login.js +0 -117
- package/dist/commands/logout.d.ts +0 -5
- package/dist/commands/logout.js +0 -17
- package/dist/commands/mcp-setup.d.ts +0 -5
- package/dist/commands/mcp-setup.js +0 -431
- package/dist/commands/test.d.ts +0 -6
- package/dist/commands/test.js +0 -78
- package/dist/mcp/handlers/coverage.d.ts +0 -44
- package/dist/mcp/handlers/coverage.js +0 -105
- package/dist/mcp/types.d.ts +0 -27
- package/dist/mcp/types.js +0 -2
- package/dist/utils/local-project.d.ts +0 -31
- package/dist/utils/local-project.js +0 -33
- package/dist/utils/token-refresh.d.ts +0 -24
- package/dist/utils/token-refresh.js +0 -69
- package/dist/utils/token-storage.d.ts +0 -31
- package/dist/utils/token-storage.js +0 -57
- /package/dist/{mcp → requirements}/testCodeExtractor.d.ts +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { relative, resolve } from "node:path";
|
|
3
|
+
import { findAllTestReferences } from "../requirements/grep.js";
|
|
4
|
+
import { loadAllRequirements } from "../requirements/index.js";
|
|
5
|
+
export async function testsForCommand(requirementsFile) {
|
|
6
|
+
const workspaceRoot = process.cwd();
|
|
7
|
+
const fullPath = resolve(workspaceRoot, requirementsFile);
|
|
8
|
+
// CLI-FIND-MAP-2.3: missing file or wrong extension → error + non-zero exit
|
|
9
|
+
if (!existsSync(fullPath)) {
|
|
10
|
+
throw new Error(`Requirements file not found: ${requirementsFile}`);
|
|
11
|
+
}
|
|
12
|
+
if (!requirementsFile.endsWith(".requirements.md")) {
|
|
13
|
+
throw new Error(`File must be a requirements file: *.requirements.md`);
|
|
14
|
+
}
|
|
15
|
+
const { flattened } = await loadAllRequirements(workspaceRoot);
|
|
16
|
+
const fileRequirements = flattened.filter((req) => resolve(workspaceRoot, req.sourceFile) === fullPath);
|
|
17
|
+
if (fileRequirements.length === 0) {
|
|
18
|
+
console.log(`No requirements found in "${requirementsFile}".`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const rootIds = new Set(fileRequirements.map((req) => req.rootId));
|
|
22
|
+
const allTestRefs = await findAllTestReferences(workspaceRoot);
|
|
23
|
+
// Build coverage map: requirement ID → test refs
|
|
24
|
+
const coverageMap = new Map();
|
|
25
|
+
for (const ref of allTestRefs) {
|
|
26
|
+
if (!coverageMap.has(ref.requirementId)) {
|
|
27
|
+
coverageMap.set(ref.requirementId, []);
|
|
28
|
+
}
|
|
29
|
+
coverageMap.get(ref.requirementId).push(ref);
|
|
30
|
+
}
|
|
31
|
+
// CLI-FIND-MAP-2.0: group by coverage status
|
|
32
|
+
const covered = [];
|
|
33
|
+
const notCovered = [];
|
|
34
|
+
for (const rootId of rootIds) {
|
|
35
|
+
const reqAndChildren = fileRequirements.filter((r) => r.rootId === rootId);
|
|
36
|
+
const allIds = reqAndChildren.map((r) => r.id);
|
|
37
|
+
const testsForThisReq = [];
|
|
38
|
+
for (const id of allIds) {
|
|
39
|
+
const refs = coverageMap.get(id);
|
|
40
|
+
if (refs) {
|
|
41
|
+
testsForThisReq.push(...refs);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (testsForThisReq.length > 0) {
|
|
45
|
+
covered.push({ id: rootId, tests: testsForThisReq });
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
notCovered.push(rootId);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
console.log(`Test coverage for "${requirementsFile}":\n`);
|
|
52
|
+
if (covered.length > 0) {
|
|
53
|
+
console.log(`Covered (${covered.length}):`);
|
|
54
|
+
// CLI-FIND-MAP-2.1: test file path + line numbers for each covered req
|
|
55
|
+
for (const { id, tests } of covered) {
|
|
56
|
+
const testsByFile = new Map();
|
|
57
|
+
for (const test of tests) {
|
|
58
|
+
if (!testsByFile.has(test.file)) {
|
|
59
|
+
testsByFile.set(test.file, []);
|
|
60
|
+
}
|
|
61
|
+
testsByFile.get(test.file).push(test.line);
|
|
62
|
+
}
|
|
63
|
+
console.log(` ${id}: ${tests.length} test${tests.length === 1 ? "" : "s"}`);
|
|
64
|
+
for (const [file, lines] of testsByFile) {
|
|
65
|
+
const relPath = relative(workspaceRoot, file);
|
|
66
|
+
const lineList = lines.sort((a, b) => a - b).join(", ");
|
|
67
|
+
console.log(` ${relPath} (lines ${lineList})`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
console.log();
|
|
71
|
+
}
|
|
72
|
+
// CLI-FIND-MAP-2.2: uncovered group
|
|
73
|
+
if (notCovered.length > 0) {
|
|
74
|
+
console.log(`Not Covered (${notCovered.length}):`);
|
|
75
|
+
for (const id of notCovered) {
|
|
76
|
+
console.log(` ${id}: No tests found`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=tests-for.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { parseRequirementsFromFile, } from "../schema/index.js";
|
|
4
|
+
export async function validateCommand(options) {
|
|
5
|
+
console.log("Parsing and validating requirements...");
|
|
6
|
+
const requirementsDir = path.join(process.cwd(), ".requirements");
|
|
7
|
+
if (!fs.existsSync(requirementsDir)) {
|
|
8
|
+
console.error("Error: .requirements/ directory not found.");
|
|
9
|
+
console.log('Run "dotrequirements pull" first to sync requirements.');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
// Get files to test
|
|
13
|
+
let files;
|
|
14
|
+
if (options.file) {
|
|
15
|
+
files = [options.file];
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
files = fs
|
|
19
|
+
.readdirSync(requirementsDir)
|
|
20
|
+
.filter((f) => f.endsWith(".requirements.md"))
|
|
21
|
+
.map((f) => path.join(requirementsDir, f));
|
|
22
|
+
}
|
|
23
|
+
if (files.length === 0) {
|
|
24
|
+
console.log("No Markdown requirements files found to test.");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
let totalRequirements = 0;
|
|
28
|
+
let passCount = 0;
|
|
29
|
+
let failCount = 0;
|
|
30
|
+
for (const filePath of files) {
|
|
31
|
+
const fileName = path.basename(filePath);
|
|
32
|
+
console.log(`\nTesting: ${fileName}`);
|
|
33
|
+
try {
|
|
34
|
+
// Parse and validate using shared schema module
|
|
35
|
+
const { metadata, requirements } = parseRequirementsFromFile(filePath);
|
|
36
|
+
// Count total requirements (root + all descendants)
|
|
37
|
+
let count = 0;
|
|
38
|
+
for (const req of requirements) {
|
|
39
|
+
count += countRequirementsInTree(req);
|
|
40
|
+
}
|
|
41
|
+
totalRequirements += count;
|
|
42
|
+
console.log(` ✓ Valid structure (${count} requirement(s))`);
|
|
43
|
+
console.log(` ✓ Version: ${metadata.version}`);
|
|
44
|
+
console.log(` ✓ Last sync: ${metadata.pulledAt}`);
|
|
45
|
+
passCount++;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error(` ✗ Validation failed:`, error);
|
|
49
|
+
failCount++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
console.log(`\n${"=".repeat(50)}`);
|
|
53
|
+
console.log(`Total files: ${files.length}`);
|
|
54
|
+
console.log(`Passed: ${passCount}`);
|
|
55
|
+
console.log(`Failed: ${failCount}`);
|
|
56
|
+
console.log(`Total requirements: ${totalRequirements}`);
|
|
57
|
+
if (failCount > 0) {
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Recursively count requirements in a tree (1 for root + all descendants)
|
|
63
|
+
*/
|
|
64
|
+
function countRequirementsInTree(node) {
|
|
65
|
+
let count = 1; // Count this node
|
|
66
|
+
// Count all children
|
|
67
|
+
for (const child of node.children) {
|
|
68
|
+
count += countRequirementsInTree(child);
|
|
69
|
+
}
|
|
70
|
+
return count;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=validate.js.map
|
package/dist/config.js
CHANGED
package/dist/convex.d.ts
CHANGED
|
@@ -1,66 +1,78 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Convex API stub for CLI usage
|
|
3
|
-
*
|
|
2
|
+
* Convex API stub for CLI usage.
|
|
3
|
+
*
|
|
4
|
+
* The CLI doesn't import generated convex types (those live in the convex
|
|
5
|
+
* package and aren't shipped to npm consumers), so we hand-roll the `api`
|
|
6
|
+
* object that gets passed into `ConvexHttpClient.query/mutation/action`.
|
|
7
|
+
*
|
|
8
|
+
* `makeFunctionReference<"query" | "mutation" | "action">("path:fn")` is the
|
|
9
|
+
* official escape hatch for this exact case — it builds a `FunctionReference`
|
|
10
|
+
* with the right shape for the convex client to consume, without us having to
|
|
11
|
+
* cast through `any`.
|
|
12
|
+
*
|
|
13
|
+
* The args/return types default to `any` inside the convex library types
|
|
14
|
+
* (that's `FunctionReference`'s public surface), but at the call site we still
|
|
15
|
+
* get a properly typed reference object — no user-authored `any` here.
|
|
4
16
|
*/
|
|
5
17
|
export declare const api: {
|
|
6
18
|
documents: {
|
|
7
19
|
queries: {
|
|
8
|
-
exportForCli: any
|
|
9
|
-
exportProjectForCli: any
|
|
10
|
-
checkDocumentsExist: any
|
|
11
|
-
getDocumentsMetadata: any
|
|
20
|
+
exportForCli: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
|
|
21
|
+
exportProjectForCli: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
|
|
22
|
+
checkDocumentsExist: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
|
|
23
|
+
getDocumentsMetadata: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
|
|
12
24
|
};
|
|
13
25
|
mutations: {
|
|
14
|
-
create: any
|
|
15
|
-
update: any
|
|
26
|
+
create: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
|
|
27
|
+
update: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
|
|
16
28
|
};
|
|
17
29
|
saveWithRequirements: {
|
|
18
|
-
saveWithRequirements: any
|
|
30
|
+
saveWithRequirements: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
|
|
19
31
|
};
|
|
20
32
|
};
|
|
21
33
|
requirements: {
|
|
22
34
|
queries: {
|
|
23
|
-
listByProject: any
|
|
35
|
+
listByProject: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
|
|
24
36
|
};
|
|
25
37
|
mutations: {
|
|
26
|
-
bulkUpdate: any
|
|
38
|
+
bulkUpdate: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
|
|
27
39
|
};
|
|
28
40
|
};
|
|
29
41
|
projects: {
|
|
30
42
|
mutations: {
|
|
31
|
-
create: any
|
|
43
|
+
create: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
|
|
32
44
|
};
|
|
33
45
|
};
|
|
34
46
|
teams: {
|
|
35
47
|
queries: {
|
|
36
|
-
listForUserWithUsage: any
|
|
48
|
+
listForUserWithUsage: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
|
|
37
49
|
};
|
|
38
50
|
mutations: {
|
|
39
|
-
ensureDefaultTeam: any
|
|
51
|
+
ensureDefaultTeam: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
|
|
40
52
|
};
|
|
41
53
|
};
|
|
42
54
|
teamInvites: {
|
|
43
55
|
queries: {
|
|
44
|
-
getInviteInfo: any
|
|
56
|
+
getInviteInfo: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
|
|
45
57
|
};
|
|
46
58
|
};
|
|
47
59
|
polar: {
|
|
48
|
-
acceptTeamInvite: any
|
|
60
|
+
acceptTeamInvite: import("convex/server").FunctionReference<"action", "public", any, any, string | undefined>;
|
|
49
61
|
};
|
|
50
62
|
projectSecrets: {
|
|
51
63
|
queries: {
|
|
52
|
-
getOwnSecret: any
|
|
53
|
-
listOwnSecretsForTeam: any
|
|
64
|
+
getOwnSecret: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
|
|
65
|
+
listOwnSecretsForTeam: import("convex/server").FunctionReference<"query", "public", any, any, string | undefined>;
|
|
54
66
|
};
|
|
55
67
|
mutations: {
|
|
56
|
-
ensureOwnSecret: any
|
|
57
|
-
revokeOwnSecret: any
|
|
68
|
+
ensureOwnSecret: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
|
|
69
|
+
revokeOwnSecret: import("convex/server").FunctionReference<"mutation", "public", any, any, string | undefined>;
|
|
58
70
|
};
|
|
59
71
|
};
|
|
60
72
|
lib: {
|
|
61
73
|
authActions: {
|
|
62
|
-
exchangeAuthorizationCode: any
|
|
63
|
-
refreshAccessToken: any
|
|
74
|
+
exchangeAuthorizationCode: import("convex/server").FunctionReference<"action", "public", any, any, string | undefined>;
|
|
75
|
+
refreshAccessToken: import("convex/server").FunctionReference<"action", "public", any, any, string | undefined>;
|
|
64
76
|
};
|
|
65
77
|
};
|
|
66
78
|
};
|
package/dist/convex.js
CHANGED
|
@@ -1,68 +1,84 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Convex API stub for CLI usage
|
|
3
|
-
*
|
|
2
|
+
* Convex API stub for CLI usage.
|
|
3
|
+
*
|
|
4
|
+
* The CLI doesn't import generated convex types (those live in the convex
|
|
5
|
+
* package and aren't shipped to npm consumers), so we hand-roll the `api`
|
|
6
|
+
* object that gets passed into `ConvexHttpClient.query/mutation/action`.
|
|
7
|
+
*
|
|
8
|
+
* `makeFunctionReference<"query" | "mutation" | "action">("path:fn")` is the
|
|
9
|
+
* official escape hatch for this exact case — it builds a `FunctionReference`
|
|
10
|
+
* with the right shape for the convex client to consume, without us having to
|
|
11
|
+
* cast through `any`.
|
|
12
|
+
*
|
|
13
|
+
* The args/return types default to `any` inside the convex library types
|
|
14
|
+
* (that's `FunctionReference`'s public surface), but at the call site we still
|
|
15
|
+
* get a properly typed reference object — no user-authored `any` here.
|
|
4
16
|
*/
|
|
17
|
+
import { makeFunctionReference } from "convex/server";
|
|
18
|
+
const query = (name) => makeFunctionReference(name);
|
|
19
|
+
const mutation = (name) => makeFunctionReference(name);
|
|
20
|
+
const action = (name) => makeFunctionReference(name);
|
|
5
21
|
export const api = {
|
|
6
22
|
documents: {
|
|
7
23
|
queries: {
|
|
8
|
-
exportForCli:
|
|
9
|
-
exportProjectForCli:
|
|
10
|
-
checkDocumentsExist:
|
|
24
|
+
exportForCli: query("documents/queries:exportForCli"),
|
|
25
|
+
exportProjectForCli: query("documents/queries:exportProjectForCli"),
|
|
26
|
+
checkDocumentsExist: query("documents/queries:checkDocumentsExist"),
|
|
11
27
|
// SYNC-CLI-EDIT-2: Get document metadata for conflict detection
|
|
12
|
-
getDocumentsMetadata:
|
|
28
|
+
getDocumentsMetadata: query("documents/queries:getDocumentsMetadata"),
|
|
13
29
|
},
|
|
14
30
|
mutations: {
|
|
15
|
-
create:
|
|
16
|
-
update:
|
|
31
|
+
create: mutation("documents/mutations:create"),
|
|
32
|
+
update: mutation("documents/mutations:update"),
|
|
17
33
|
},
|
|
18
34
|
// SYNC-ARCH-1: Save document with requirements derivation
|
|
19
35
|
saveWithRequirements: {
|
|
20
|
-
saveWithRequirements:
|
|
36
|
+
saveWithRequirements: mutation("documents/saveWithRequirements:saveWithRequirements"),
|
|
21
37
|
},
|
|
22
38
|
},
|
|
23
39
|
requirements: {
|
|
24
40
|
queries: {
|
|
25
|
-
listByProject:
|
|
41
|
+
listByProject: query("requirements/queries:listByProject"),
|
|
26
42
|
},
|
|
27
43
|
mutations: {
|
|
28
|
-
bulkUpdate:
|
|
44
|
+
bulkUpdate: mutation("requirements/mutations:bulkUpdate"),
|
|
29
45
|
},
|
|
30
46
|
},
|
|
31
47
|
projects: {
|
|
32
48
|
mutations: {
|
|
33
|
-
create:
|
|
49
|
+
create: mutation("projects/mutations:create"),
|
|
34
50
|
},
|
|
35
51
|
},
|
|
36
52
|
teams: {
|
|
37
53
|
queries: {
|
|
38
|
-
listForUserWithUsage:
|
|
54
|
+
listForUserWithUsage: query("teams/queries:listForUserWithUsage"),
|
|
39
55
|
},
|
|
40
56
|
mutations: {
|
|
41
|
-
ensureDefaultTeam:
|
|
57
|
+
ensureDefaultTeam: mutation("teams/mutations:ensureDefaultTeam"),
|
|
42
58
|
},
|
|
43
59
|
},
|
|
44
60
|
teamInvites: {
|
|
45
61
|
queries: {
|
|
46
|
-
getInviteInfo:
|
|
62
|
+
getInviteInfo: query("teamInvites/queries:getInviteInfo"),
|
|
47
63
|
},
|
|
48
64
|
},
|
|
49
65
|
polar: {
|
|
50
|
-
acceptTeamInvite:
|
|
66
|
+
acceptTeamInvite: action("polar:acceptTeamInvite"),
|
|
51
67
|
},
|
|
52
68
|
projectSecrets: {
|
|
53
69
|
queries: {
|
|
54
|
-
getOwnSecret:
|
|
55
|
-
listOwnSecretsForTeam:
|
|
70
|
+
getOwnSecret: query("projectSecrets/queries:getOwnSecret"),
|
|
71
|
+
listOwnSecretsForTeam: query("projectSecrets/queries:listOwnSecretsForTeam"),
|
|
56
72
|
},
|
|
57
73
|
mutations: {
|
|
58
|
-
ensureOwnSecret:
|
|
59
|
-
revokeOwnSecret:
|
|
74
|
+
ensureOwnSecret: mutation("projectSecrets/mutations:ensureOwnSecret"),
|
|
75
|
+
revokeOwnSecret: mutation("projectSecrets/mutations:revokeOwnSecret"),
|
|
60
76
|
},
|
|
61
77
|
},
|
|
62
78
|
lib: {
|
|
63
79
|
authActions: {
|
|
64
|
-
exchangeAuthorizationCode:
|
|
65
|
-
refreshAccessToken:
|
|
80
|
+
exchangeAuthorizationCode: action("lib/authActions:exchangeAuthorizationCode"),
|
|
81
|
+
refreshAccessToken: action("lib/authActions:refreshAccessToken"),
|
|
66
82
|
},
|
|
67
83
|
},
|
|
68
84
|
};
|
package/dist/harness/cache.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - tracking.jsonl: Append-only tracking of requirement() calls (cross-process safe)
|
|
7
7
|
* - coverage.json: Deduplication cache for cloud reporting
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import type { RequirementNode } from "../schema/index.js";
|
|
10
10
|
/**
|
|
11
11
|
* Tracking entry for a single requirement() call.
|
|
12
12
|
* Stored as one JSON object per line in tracking.jsonl
|
|
@@ -68,10 +68,6 @@ export declare function findProjectRoot(startDir?: string): string | null;
|
|
|
68
68
|
* Get the cache directory path, creating it if necessary
|
|
69
69
|
*/
|
|
70
70
|
export declare function getCacheDir(requirementsDir: string, create?: boolean): string;
|
|
71
|
-
/**
|
|
72
|
-
* Find all *.requirements.md files under project root
|
|
73
|
-
*/
|
|
74
|
-
export declare function findRequirementsFiles(projectRoot: string): string[];
|
|
75
71
|
/**
|
|
76
72
|
* Write the lookup cache with all pre-parsed requirements
|
|
77
73
|
*/
|
package/dist/harness/cache.js
CHANGED
|
@@ -6,25 +6,26 @@
|
|
|
6
6
|
* - tracking.jsonl: Append-only tracking of requirement() calls (cross-process safe)
|
|
7
7
|
* - coverage.json: Deduplication cache for cloud reporting
|
|
8
8
|
*/
|
|
9
|
-
import * as fs from
|
|
10
|
-
import * as path from
|
|
11
|
-
import {
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import { findRequirementsFilesSync } from "../requirements/index.js";
|
|
12
12
|
// Cache directory structure
|
|
13
|
-
const CACHE_DIR =
|
|
14
|
-
const LOOKUP_FILE =
|
|
15
|
-
const TRACKING_FILE =
|
|
16
|
-
const COVERAGE_FILE =
|
|
17
|
-
const PROJECT_ROOT_FILE =
|
|
13
|
+
const CACHE_DIR = ".cache";
|
|
14
|
+
const LOOKUP_FILE = "lookup.json";
|
|
15
|
+
const TRACKING_FILE = "tracking.jsonl";
|
|
16
|
+
const COVERAGE_FILE = "coverage.json";
|
|
17
|
+
const PROJECT_ROOT_FILE = "project-root";
|
|
18
18
|
// Test run ID file (outside cache, in .requirements/)
|
|
19
|
-
const TEST_RUN_ID_FILE =
|
|
19
|
+
const TEST_RUN_ID_FILE = ".test-run-id";
|
|
20
20
|
/**
|
|
21
21
|
* Find the nearest .requirements directory by walking up from startDir
|
|
22
22
|
*/
|
|
23
23
|
export function findRequirementsDir(startDir = process.cwd()) {
|
|
24
24
|
let currentDir = path.resolve(startDir);
|
|
25
25
|
while (true) {
|
|
26
|
-
const requirementsDir = path.join(currentDir,
|
|
27
|
-
if (fs.existsSync(requirementsDir) &&
|
|
26
|
+
const requirementsDir = path.join(currentDir, ".requirements");
|
|
27
|
+
if (fs.existsSync(requirementsDir) &&
|
|
28
|
+
fs.statSync(requirementsDir).isDirectory()) {
|
|
28
29
|
return requirementsDir;
|
|
29
30
|
}
|
|
30
31
|
const parentDir = path.dirname(currentDir);
|
|
@@ -52,28 +53,6 @@ export function getCacheDir(requirementsDir, create = false) {
|
|
|
52
53
|
}
|
|
53
54
|
return cacheDir;
|
|
54
55
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Find all *.requirements.md files under project root
|
|
57
|
-
*/
|
|
58
|
-
export function findRequirementsFiles(projectRoot) {
|
|
59
|
-
const pattern = '**/*.requirements.md';
|
|
60
|
-
const matches = globSync(pattern, {
|
|
61
|
-
cwd: projectRoot,
|
|
62
|
-
ignore: [
|
|
63
|
-
'**/node_modules/**',
|
|
64
|
-
'**/dist/**',
|
|
65
|
-
'**/.git/**',
|
|
66
|
-
'**/build/**',
|
|
67
|
-
'**/example/**',
|
|
68
|
-
'**/examples/**',
|
|
69
|
-
'**/__tests__/**',
|
|
70
|
-
'**/fixtures/**',
|
|
71
|
-
'**/.fixtures/**',
|
|
72
|
-
],
|
|
73
|
-
dot: true, // Include dotfiles/dotdirs like .requirements/
|
|
74
|
-
});
|
|
75
|
-
return matches.map(m => path.join(projectRoot, m));
|
|
76
|
-
}
|
|
77
56
|
/**
|
|
78
57
|
* Write the lookup cache with all pre-parsed requirements
|
|
79
58
|
*/
|
|
@@ -101,14 +80,15 @@ export function writeLookupCache(requirementsDir, requirements) {
|
|
|
101
80
|
const labelCounts = new Map();
|
|
102
81
|
for (const child of node.children) {
|
|
103
82
|
let childLabelPath = null;
|
|
104
|
-
if (child.label && child.label !==
|
|
105
|
-
const normalizedLabel = child.label.toLowerCase().replace(/\s+/g,
|
|
83
|
+
if (child.label && child.label !== "requirementHeader") {
|
|
84
|
+
const normalizedLabel = child.label.toLowerCase().replace(/\s+/g, "-");
|
|
106
85
|
const count = labelCounts.get(normalizedLabel) || 0;
|
|
107
86
|
labelCounts.set(normalizedLabel, count + 1);
|
|
108
87
|
const parentPath = labelPath || node.id;
|
|
109
|
-
childLabelPath =
|
|
110
|
-
|
|
111
|
-
|
|
88
|
+
childLabelPath =
|
|
89
|
+
count === 0
|
|
90
|
+
? `${parentPath}.${normalizedLabel}`
|
|
91
|
+
: `${parentPath}.${normalizedLabel}#${count}`;
|
|
112
92
|
}
|
|
113
93
|
flatten(child, childLabelPath);
|
|
114
94
|
}
|
|
@@ -124,7 +104,7 @@ export function writeLookupCache(requirementsDir, requirements) {
|
|
|
124
104
|
*/
|
|
125
105
|
export function isCacheStale(requirementsDir, cacheMtime) {
|
|
126
106
|
const projectRoot = path.dirname(requirementsDir);
|
|
127
|
-
const sourceFiles =
|
|
107
|
+
const sourceFiles = findRequirementsFilesSync(projectRoot);
|
|
128
108
|
for (const file of sourceFiles) {
|
|
129
109
|
try {
|
|
130
110
|
const stat = fs.statSync(file);
|
|
@@ -159,7 +139,7 @@ export function readLookupCache(requirementsDir) {
|
|
|
159
139
|
return null;
|
|
160
140
|
}
|
|
161
141
|
try {
|
|
162
|
-
const content = fs.readFileSync(lookupPath,
|
|
142
|
+
const content = fs.readFileSync(lookupPath, "utf-8");
|
|
163
143
|
return JSON.parse(content);
|
|
164
144
|
}
|
|
165
145
|
catch {
|
|
@@ -183,7 +163,7 @@ export function getTestRunId(requirementsDir) {
|
|
|
183
163
|
if (!fs.existsSync(testRunIdPath)) {
|
|
184
164
|
return null;
|
|
185
165
|
}
|
|
186
|
-
return fs.readFileSync(testRunIdPath,
|
|
166
|
+
return fs.readFileSync(testRunIdPath, "utf-8").trim();
|
|
187
167
|
}
|
|
188
168
|
/**
|
|
189
169
|
* Clean up the test run ID file
|
|
@@ -212,7 +192,7 @@ export function readProjectRoot(requirementsDir) {
|
|
|
212
192
|
if (!fs.existsSync(projectRootPath)) {
|
|
213
193
|
return null;
|
|
214
194
|
}
|
|
215
|
-
return fs.readFileSync(projectRootPath,
|
|
195
|
+
return fs.readFileSync(projectRootPath, "utf-8").trim();
|
|
216
196
|
}
|
|
217
197
|
/**
|
|
218
198
|
* Try to find the project root from any known .requirements cache directory.
|
|
@@ -221,10 +201,10 @@ export function readProjectRoot(requirementsDir) {
|
|
|
221
201
|
export function findCachedProjectRoot(startDir = process.cwd()) {
|
|
222
202
|
let currentDir = path.resolve(startDir);
|
|
223
203
|
while (true) {
|
|
224
|
-
const requirementsDir = path.join(currentDir,
|
|
204
|
+
const requirementsDir = path.join(currentDir, ".requirements");
|
|
225
205
|
const projectRootPath = path.join(requirementsDir, CACHE_DIR, PROJECT_ROOT_FILE);
|
|
226
206
|
if (fs.existsSync(projectRootPath)) {
|
|
227
|
-
return fs.readFileSync(projectRootPath,
|
|
207
|
+
return fs.readFileSync(projectRootPath, "utf-8").trim();
|
|
228
208
|
}
|
|
229
209
|
const parentDir = path.dirname(currentDir);
|
|
230
210
|
if (parentDir === currentDir) {
|
|
@@ -249,7 +229,7 @@ export function clearTrackingFile(requirementsDir) {
|
|
|
249
229
|
export function appendTrackingEntry(requirementsDir, entry) {
|
|
250
230
|
const cacheDir = getCacheDir(requirementsDir, true);
|
|
251
231
|
const trackingPath = path.join(cacheDir, TRACKING_FILE);
|
|
252
|
-
const line = JSON.stringify(entry)
|
|
232
|
+
const line = `${JSON.stringify(entry)}\n`;
|
|
253
233
|
fs.appendFileSync(trackingPath, line);
|
|
254
234
|
}
|
|
255
235
|
/**
|
|
@@ -261,9 +241,9 @@ export function readTrackingEntries(requirementsDir) {
|
|
|
261
241
|
if (!fs.existsSync(trackingPath)) {
|
|
262
242
|
return [];
|
|
263
243
|
}
|
|
264
|
-
const content = fs.readFileSync(trackingPath,
|
|
244
|
+
const content = fs.readFileSync(trackingPath, "utf-8");
|
|
265
245
|
const entries = [];
|
|
266
|
-
for (const line of content.split(
|
|
246
|
+
for (const line of content.split("\n")) {
|
|
267
247
|
if (line.trim()) {
|
|
268
248
|
try {
|
|
269
249
|
entries.push(JSON.parse(line));
|
|
@@ -294,20 +274,28 @@ export function deleteTrackingFile(requirementsDir) {
|
|
|
294
274
|
* can treat both shapes uniformly.
|
|
295
275
|
*/
|
|
296
276
|
function normalizeEntry(raw) {
|
|
297
|
-
if (!raw || typeof raw !==
|
|
277
|
+
if (!raw || typeof raw !== "object")
|
|
278
|
+
return null;
|
|
279
|
+
// Cache entry shape from disk; the guards below check each field at runtime.
|
|
280
|
+
const entry = raw;
|
|
281
|
+
if (typeof entry.testRunId !== "string")
|
|
282
|
+
return null;
|
|
283
|
+
if (typeof entry.timestamp !== "number")
|
|
298
284
|
return null;
|
|
299
|
-
if (Array.isArray(
|
|
285
|
+
if (Array.isArray(entry.tuples)) {
|
|
300
286
|
return {
|
|
301
|
-
testRunId:
|
|
302
|
-
timestamp:
|
|
303
|
-
tuples:
|
|
287
|
+
testRunId: entry.testRunId,
|
|
288
|
+
timestamp: entry.timestamp,
|
|
289
|
+
tuples: entry.tuples,
|
|
304
290
|
};
|
|
305
291
|
}
|
|
306
|
-
if (Array.isArray(
|
|
292
|
+
if (Array.isArray(entry.requirementKeys)) {
|
|
307
293
|
return {
|
|
308
|
-
testRunId:
|
|
309
|
-
timestamp:
|
|
310
|
-
tuples:
|
|
294
|
+
testRunId: entry.testRunId,
|
|
295
|
+
timestamp: entry.timestamp,
|
|
296
|
+
tuples: entry.requirementKeys.map((requirementKey) => ({
|
|
297
|
+
requirementKey,
|
|
298
|
+
})),
|
|
311
299
|
};
|
|
312
300
|
}
|
|
313
301
|
return null;
|
|
@@ -319,7 +307,7 @@ export function readCoverageCache(requirementsDir) {
|
|
|
319
307
|
return { current: null, previous: null };
|
|
320
308
|
}
|
|
321
309
|
try {
|
|
322
|
-
const content = fs.readFileSync(coveragePath,
|
|
310
|
+
const content = fs.readFileSync(coveragePath, "utf-8");
|
|
323
311
|
const raw = JSON.parse(content);
|
|
324
312
|
return {
|
|
325
313
|
current: normalizeEntry(raw?.current),
|
|
@@ -337,7 +325,7 @@ export function readCoverageCache(requirementsDir) {
|
|
|
337
325
|
export function updateCoverageCache(requirementsDir, testRunId, tuples) {
|
|
338
326
|
const cacheDir = getCacheDir(requirementsDir, true);
|
|
339
327
|
const coveragePath = path.join(cacheDir, COVERAGE_FILE);
|
|
340
|
-
const normalized = tuples.map((t) => typeof t ===
|
|
328
|
+
const normalized = tuples.map((t) => typeof t === "string" ? { requirementKey: t } : t);
|
|
341
329
|
const existing = readCoverageCache(requirementsDir);
|
|
342
330
|
const newCache = {
|
|
343
331
|
current: {
|
|
@@ -360,7 +348,9 @@ function tuplesMatch(a, b) {
|
|
|
360
348
|
* Returns true if no matching tuple was reported within the debounce window.
|
|
361
349
|
*/
|
|
362
350
|
export function needsReporting(tupleOrKey, cache) {
|
|
363
|
-
const tuple = typeof tupleOrKey ===
|
|
351
|
+
const tuple = typeof tupleOrKey === "string"
|
|
352
|
+
? { requirementKey: tupleOrKey }
|
|
353
|
+
: tupleOrKey;
|
|
364
354
|
// If no current run, everything needs reporting
|
|
365
355
|
if (!cache.current) {
|
|
366
356
|
return true;
|