alp-code 0.9.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/CHANGELOG.md +770 -0
- package/LICENSE +21 -0
- package/README.md +295 -0
- package/alp.config.yaml +5 -0
- package/dist/src/agents/agent-definition.js +28 -0
- package/dist/src/agents/capability-catalog.js +33 -0
- package/dist/src/agents/compaction.js +36 -0
- package/dist/src/agents/errors.js +12 -0
- package/dist/src/agents/librarian.js +38 -0
- package/dist/src/agents/main.js +37 -0
- package/dist/src/agents/memory-grant.js +29 -0
- package/dist/src/agents/model-context.js +70 -0
- package/dist/src/agents/modes.js +134 -0
- package/dist/src/agents/oracle.js +36 -0
- package/dist/src/agents/read-thread.js +38 -0
- package/dist/src/agents/registry.js +238 -0
- package/dist/src/agents/render-identity.js +38 -0
- package/dist/src/agents/review.js +37 -0
- package/dist/src/agents/search.js +37 -0
- package/dist/src/agents/shared/house-rules.js +33 -0
- package/dist/src/agents/shared/principal.js +18 -0
- package/dist/src/agents/shared/voice.js +29 -0
- package/dist/src/agents/titling.js +32 -0
- package/dist/src/agents/types.js +15 -0
- package/dist/src/backend/execution-backend.js +2 -0
- package/dist/src/backend/local-execution-store.js +144 -0
- package/dist/src/backend/local-process-backend.js +533 -0
- package/dist/src/backend/local-supervisor.js +104 -0
- package/dist/src/cli/alp.js +380 -0
- package/dist/src/cli/commands/context.js +203 -0
- package/dist/src/cli/commands/delegate.js +136 -0
- package/dist/src/cli/commands/identity-sync.js +31 -0
- package/dist/src/cli/commands/init.js +184 -0
- package/dist/src/cli/commands/mode.js +22 -0
- package/dist/src/cli/commands/principal.js +114 -0
- package/dist/src/cli/commands/run-main.js +90 -0
- package/dist/src/cli/commands/runtime.js +21 -0
- package/dist/src/cli/mode-preference-store.js +62 -0
- package/dist/src/cli/mode-selector.js +178 -0
- package/dist/src/cli/update-check.js +77 -0
- package/dist/src/context/checkpoint.js +134 -0
- package/dist/src/context/compact-journal.js +153 -0
- package/dist/src/context/compact-payload.js +121 -0
- package/dist/src/context/continuity.js +70 -0
- package/dist/src/context/types.js +2 -0
- package/dist/src/delegation/backend-registry.js +40 -0
- package/dist/src/delegation/delegation-service.js +300 -0
- package/dist/src/delegation/types.js +12 -0
- package/dist/src/execution/execution-policy.js +96 -0
- package/dist/src/execution/execution-service.js +115 -0
- package/dist/src/execution/execution-store.js +78 -0
- package/dist/src/execution/identity-capsule.js +65 -0
- package/dist/src/execution/types.js +12 -0
- package/dist/src/hooks/execution-bridge.js +84 -0
- package/dist/src/index.js +4 -0
- package/dist/src/memory/adapters/markdown-file-store.js +257 -0
- package/dist/src/memory/adapters/memory-api-client.js +2 -0
- package/dist/src/memory/adapters/memory-path-mapper.js +76 -0
- package/dist/src/memory/adapters/remote-api-store.js +25 -0
- package/dist/src/memory/context-ranker.js +21 -0
- package/dist/src/memory/errors.js +58 -0
- package/dist/src/memory/memory-service.js +149 -0
- package/dist/src/memory/memory-store.js +2 -0
- package/dist/src/memory/types.js +2 -0
- package/dist/src/policy/capability-policy.js +29 -0
- package/dist/src/policy/delegation-policy.js +25 -0
- package/dist/src/policy/errors.js +10 -0
- package/dist/src/policy/invariants.js +31 -0
- package/dist/src/policy/memory-policy.js +22 -0
- package/dist/src/policy/policy-engine.js +85 -0
- package/dist/src/policy/types.js +8 -0
- package/dist/src/policy/workspace-policy.js +77 -0
- package/dist/src/principal/principal-profile-store.js +89 -0
- package/dist/src/runtime/adapter-files.js +147 -0
- package/dist/src/runtime/claude-adapter.js +177 -0
- package/dist/src/runtime/codex-adapter.js +169 -0
- package/dist/src/runtime/permission-rules.js +156 -0
- package/dist/src/runtime/render-session-context.js +124 -0
- package/dist/src/runtime/render-task-input.js +33 -0
- package/dist/src/runtime/runtime-adapter.js +2 -0
- package/dist/src/runtime/runtime-preference-store.js +66 -0
- package/dist/src/runtime/runtime-selector.js +178 -0
- package/dist/src/runtime/types.js +2 -0
- package/dist/src/runtime/windows-shim.js +57 -0
- package/dist/src/state-paths.js +49 -0
- package/dist/src/workflow/output-validator.js +27 -0
- package/dist/src/workflow/repair-policy.js +8 -0
- package/dist/src/workflow/types.js +22 -0
- package/dist/src/workflow/workflow-runner.js +81 -0
- package/hooks/compact-record.cjs +109 -0
- package/hooks/session-boot.cjs +112 -0
- package/hooks/session-end.cjs +34 -0
- package/package.json +48 -0
- package/scaffold/memory/INDEX.md +27 -0
- package/scaffold/memory/README.md +76 -0
- package/scaffold/memory/projects/INDEX.md +22 -0
- package/scaffold/memory/projects/PROTOCOL.md +128 -0
- package/scaffold/memory/projects/_template/PROJECT.md +45 -0
- package/scripts/alp.cjs +126 -0
- package/scripts/alp.ps1 +4 -0
- package/scripts/alp.sh +3 -0
- package/scripts/bootstrap.cjs +144 -0
- package/scripts/checkout-release.cjs +30 -0
- package/scripts/delegate.cjs +19 -0
- package/scripts/doctor.cjs +158 -0
- package/scripts/doctor.sh +3 -0
- package/scripts/ensure-state.cjs +22 -0
- package/scripts/lib/cli-link.cjs +375 -0
- package/scripts/lib/codex-role.cjs +18 -0
- package/scripts/lib/delegation/command-runner.cjs +108 -0
- package/scripts/lib/delegation/config.cjs +81 -0
- package/scripts/lib/install-paths.cjs +154 -0
- package/scripts/lib/release-manifest.cjs +42 -0
- package/scripts/lib/semver-lite.cjs +20 -0
- package/scripts/lib/state.cjs +274 -0
- package/scripts/lib/uninstall.cjs +252 -0
- package/scripts/lib/update-check-worker.cjs +21 -0
- package/scripts/lib/update.cjs +395 -0
- package/scripts/run-role.cjs +42 -0
- package/scripts/run-role.ps1 +4 -0
- package/scripts/run-role.sh +3 -0
- package/scripts/sync-project-index.sh +167 -0
- package/skills/agent-memory/SKILL.md +109 -0
- package/skills/alp-debug/SKILL.md +90 -0
- package/skills/alp-debug/references/defense-in-depth.md +118 -0
- package/skills/alp-debug/references/investigation-methodology.md +106 -0
- package/skills/alp-debug/references/log-and-ci-analysis.md +96 -0
- package/skills/alp-debug/references/performance-diagnostics.md +112 -0
- package/skills/alp-debug/references/reporting-standards.md +120 -0
- package/skills/alp-debug/references/root-cause-tracing.md +134 -0
- package/skills/alp-debug/references/systematic-debugging.md +93 -0
- package/skills/alp-debug/references/verification.md +86 -0
- package/skills/alp-debug/scripts/find-polluter.sh +63 -0
- package/skills/alp-debug/scripts/find-polluter.test.md +102 -0
- package/skills/alp-plan/SKILL.md +128 -0
- package/skills/alp-plan/references/archive-workflow.md +77 -0
- package/skills/alp-plan/references/codebase-understanding.md +55 -0
- package/skills/alp-plan/references/output-standards.md +96 -0
- package/skills/alp-plan/references/plan-organization.md +129 -0
- package/skills/alp-plan/references/red-team-personas.md +76 -0
- package/skills/alp-plan/references/red-team-workflow.md +81 -0
- package/skills/alp-plan/references/research-phase.md +57 -0
- package/skills/alp-plan/references/scope-challenge.md +82 -0
- package/skills/alp-plan/references/solution-design.md +76 -0
- package/skills/alp-plan/references/validate-question-framework.md +89 -0
- package/skills/alp-plan/references/validate-workflow.md +83 -0
- package/skills/alp-predict/SKILL.md +98 -0
- package/skills/alp-scenario/SKILL.md +86 -0
- package/skills/code-review/SKILL.md +111 -0
- package/skills/code-review/references/code-review-reception.md +114 -0
- package/skills/code-review/references/edge-case-scouting.md +78 -0
- package/skills/code-review/references/verification-before-completion.md +117 -0
- package/skills/delegation/SKILL.md +46 -0
- package/skills/docs-seeker/.env.example +15 -0
- package/skills/docs-seeker/SKILL.md +87 -0
- package/skills/docs-seeker/package.json +25 -0
- package/skills/docs-seeker/references/advanced.md +82 -0
- package/skills/docs-seeker/references/context7-patterns.md +68 -0
- package/skills/docs-seeker/references/errors.md +72 -0
- package/skills/docs-seeker/scripts/analyze-llms-txt.js +211 -0
- package/skills/docs-seeker/scripts/detect-topic.js +172 -0
- package/skills/docs-seeker/scripts/fetch-docs.js +213 -0
- package/skills/docs-seeker/scripts/tests/run-tests.js +72 -0
- package/skills/docs-seeker/scripts/tests/test-analyze-llms.js +119 -0
- package/skills/docs-seeker/scripts/tests/test-detect-topic.js +112 -0
- package/skills/docs-seeker/scripts/tests/test-fetch-docs.js +84 -0
- package/skills/docs-seeker/scripts/utils/env-loader.js +94 -0
- package/skills/docs-seeker/workflows/library-search.md +73 -0
- package/skills/docs-seeker/workflows/repo-analysis.md +90 -0
- package/skills/docs-seeker/workflows/topic-search.md +69 -0
- package/skills/git/SKILL.md +121 -0
- package/skills/git/references/branch-management.md +90 -0
- package/skills/git/references/commit-standards.md +82 -0
- package/skills/git/references/gh-cli-guide.md +132 -0
- package/skills/git/references/safety-protocols.md +86 -0
- package/skills/git/references/workflow-commit.md +89 -0
- package/skills/git/references/workflow-merge.md +63 -0
- package/skills/git/references/workflow-pr.md +70 -0
- package/skills/git/references/workflow-push.md +62 -0
- package/skills/gkg/SKILL.md +87 -0
- package/skills/gkg/references/cli-commands.md +92 -0
- package/skills/gkg/references/http-api.md +99 -0
- package/skills/gkg/references/language-support.md +54 -0
- package/skills/problem-solving/SKILL.md +86 -0
- package/skills/problem-solving/references/attribution.md +48 -0
- package/skills/problem-solving/references/collision-zone-thinking.md +71 -0
- package/skills/problem-solving/references/inversion-exercise.md +88 -0
- package/skills/problem-solving/references/meta-pattern-recognition.md +80 -0
- package/skills/problem-solving/references/scale-game.md +82 -0
- package/skills/problem-solving/references/simplification-cascades.md +83 -0
- package/skills/problem-solving/references/when-stuck.md +76 -0
- package/skills/repomix/SKILL.md +94 -0
- package/skills/repomix/references/configuration.md +134 -0
- package/skills/repomix/references/usage-patterns.md +106 -0
- package/skills/repomix/scripts/.coverage +0 -0
- package/skills/repomix/scripts/README.md +179 -0
- package/skills/repomix/scripts/repomix_batch.py +455 -0
- package/skills/repomix/scripts/repos.example.json +15 -0
- package/skills/repomix/scripts/requirements.txt +15 -0
- package/skills/repomix/scripts/tests/test_repomix_batch.py +531 -0
- package/skills/research/SKILL.md +107 -0
- package/skills/security-scan/SKILL.md +101 -0
- package/skills/security-scan/references/secret-patterns.md +75 -0
- package/skills/security-scan/references/vulnerability-patterns.md +136 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FileExecutionStore = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const promises_1 = require("node:fs/promises");
|
|
6
|
+
const node_os_1 = require("node:os");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
function assertExecutionId(id) {
|
|
9
|
+
if (id.length === 0 ||
|
|
10
|
+
id === "." ||
|
|
11
|
+
id === ".." ||
|
|
12
|
+
id.includes("/") ||
|
|
13
|
+
id.includes("\\")) {
|
|
14
|
+
throw new Error(`invalid execution ID \`${id}\``);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function pathExists(path) {
|
|
18
|
+
try {
|
|
19
|
+
await (0, promises_1.access)(path);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function writeSnapshot(path, value) {
|
|
27
|
+
await (0, promises_1.writeFile)(path, `${JSON.stringify(value, null, 2)}\n`, {
|
|
28
|
+
encoding: "utf8",
|
|
29
|
+
flag: "wx",
|
|
30
|
+
mode: 0o600,
|
|
31
|
+
});
|
|
32
|
+
await (0, promises_1.chmod)(path, 0o600);
|
|
33
|
+
}
|
|
34
|
+
class FileExecutionStore {
|
|
35
|
+
root;
|
|
36
|
+
constructor(options = {}) {
|
|
37
|
+
this.root = options.root ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".alp", "executions");
|
|
38
|
+
}
|
|
39
|
+
async create(input) {
|
|
40
|
+
if (input.policy.executionId !== input.state.executionId) {
|
|
41
|
+
throw new Error("policy and execution state IDs do not match");
|
|
42
|
+
}
|
|
43
|
+
assertExecutionId(input.policy.executionId);
|
|
44
|
+
await (0, promises_1.mkdir)(this.root, { recursive: true, mode: 0o700 });
|
|
45
|
+
await (0, promises_1.chmod)(this.root, 0o700);
|
|
46
|
+
const directory = (0, node_path_1.join)(this.root, input.policy.executionId);
|
|
47
|
+
if (await pathExists(directory)) {
|
|
48
|
+
throw new Error(`execution \`${input.policy.executionId}\` already exists`);
|
|
49
|
+
}
|
|
50
|
+
const staging = (0, node_path_1.join)(this.root, `.${input.policy.executionId}.${(0, node_crypto_1.randomUUID)()}.tmp`);
|
|
51
|
+
const policyFileName = "policy.json";
|
|
52
|
+
const stateFileName = "state.json";
|
|
53
|
+
try {
|
|
54
|
+
await (0, promises_1.mkdir)(staging, { mode: 0o700 });
|
|
55
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(staging, "runtime"), { mode: 0o700 });
|
|
56
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(staging, "context"), { mode: 0o700 });
|
|
57
|
+
await writeSnapshot((0, node_path_1.join)(staging, policyFileName), input.policy);
|
|
58
|
+
await writeSnapshot((0, node_path_1.join)(staging, stateFileName), input.state);
|
|
59
|
+
await (0, promises_1.rename)(staging, directory);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
await (0, promises_1.rm)(staging, { recursive: true, force: true });
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
const contextDirectory = (0, node_path_1.join)(directory, "context");
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
directory,
|
|
68
|
+
stateFile: (0, node_path_1.join)(directory, stateFileName),
|
|
69
|
+
policyFile: (0, node_path_1.join)(directory, policyFileName),
|
|
70
|
+
runtimeDirectory: (0, node_path_1.join)(directory, "runtime"),
|
|
71
|
+
contextDirectory,
|
|
72
|
+
checkpointFile: (0, node_path_1.join)(contextDirectory, "checkpoint.json"),
|
|
73
|
+
continuityFile: (0, node_path_1.join)(contextDirectory, "continuity.md"),
|
|
74
|
+
compactEventsFile: (0, node_path_1.join)(contextDirectory, "compact-events.jsonl"),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.FileExecutionStore = FileExecutionStore;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createIdentityCapsule = createIdentityCapsule;
|
|
4
|
+
const memory_grant_1 = require("../agents/memory-grant");
|
|
5
|
+
const types_1 = require("./types");
|
|
6
|
+
function memoryIdIsGranted(definition, id) {
|
|
7
|
+
const segments = id.split(":");
|
|
8
|
+
if (segments[0] === "private" && segments[1] !== definition.id) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return definition.capabilities.memory.read.some((grant) => (0, memory_grant_1.memoryGrantCovers)(grant, id));
|
|
12
|
+
}
|
|
13
|
+
function memoryIsGranted(definition, entry) {
|
|
14
|
+
return ((entry.scope !== "private" || entry.ownerRole === definition.id) &&
|
|
15
|
+
memoryIdIsGranted(definition, entry.id));
|
|
16
|
+
}
|
|
17
|
+
function createIdentityCapsule(input) {
|
|
18
|
+
if (input.policy.role !== input.definition.id ||
|
|
19
|
+
input.policy.definitionHash.length === 0) {
|
|
20
|
+
throw new Error("execution policy does not match the resolved definition");
|
|
21
|
+
}
|
|
22
|
+
const workflow = input.definition.workflow.states[input.workflowState.currentState];
|
|
23
|
+
if (input.workflowState.workflowId !== input.definition.workflow.id ||
|
|
24
|
+
workflow === undefined) {
|
|
25
|
+
throw new Error("workflow state does not match the resolved definition");
|
|
26
|
+
}
|
|
27
|
+
const grantedTools = new Set(input.policy.allowedTools);
|
|
28
|
+
const allowedTools = workflow.allowedTools.filter((tool) => grantedTools.has(tool));
|
|
29
|
+
const entries = input.memoryContext.entries
|
|
30
|
+
.filter((entry) => memoryIsGranted(input.definition, entry))
|
|
31
|
+
.map((entry) => ({
|
|
32
|
+
id: entry.id,
|
|
33
|
+
kind: entry.kind,
|
|
34
|
+
content: entry.content,
|
|
35
|
+
version: entry.version,
|
|
36
|
+
}));
|
|
37
|
+
const omittedEntryIds = input.memoryContext.diagnostics.omittedEntryIds.filter((id) => memoryIdIsGranted(input.definition, id));
|
|
38
|
+
return (0, types_1.deepFreezeExecutionValue)({
|
|
39
|
+
executionId: input.policy.executionId,
|
|
40
|
+
definitionHash: input.policy.definitionHash,
|
|
41
|
+
policyHash: input.policy.policyHash,
|
|
42
|
+
role: input.definition.id,
|
|
43
|
+
displayName: input.definition.displayName,
|
|
44
|
+
instructions: input.definition.instructions(),
|
|
45
|
+
task: input.task,
|
|
46
|
+
activeWorkspace: input.policy.workspace,
|
|
47
|
+
memoryContext: {
|
|
48
|
+
invariantContext: input.memoryContext.invariantContext,
|
|
49
|
+
policyContext: input.memoryContext.policyContext,
|
|
50
|
+
entries,
|
|
51
|
+
diagnostics: {
|
|
52
|
+
characterBudget: input.memoryContext.diagnostics.characterBudget,
|
|
53
|
+
charactersUsed: entries.reduce((characters, entry) => characters + entry.content.length, 0),
|
|
54
|
+
truncated: omittedEntryIds.length > 0,
|
|
55
|
+
omittedEntryIds,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
workflowState: { ...input.workflowState },
|
|
59
|
+
allowedTools,
|
|
60
|
+
outputContract: {
|
|
61
|
+
name: input.definition.output.name,
|
|
62
|
+
schema: input.definition.output.schema,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deepFreezeExecutionValue = deepFreezeExecutionValue;
|
|
4
|
+
function deepFreezeExecutionValue(value) {
|
|
5
|
+
if (value === null || typeof value !== "object" || Object.isFrozen(value)) {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
9
|
+
deepFreezeExecutionValue(value[key]);
|
|
10
|
+
}
|
|
11
|
+
return Object.freeze(value);
|
|
12
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateHookExecution = validateHookExecution;
|
|
4
|
+
exports.finalizeExecution = finalizeExecution;
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
const promises_1 = require("node:fs/promises");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const registry_1 = require("../agents/registry");
|
|
9
|
+
const execution_policy_1 = require("../execution/execution-policy");
|
|
10
|
+
const workflow_runner_1 = require("../workflow/workflow-runner");
|
|
11
|
+
async function validateHookExecution(input) {
|
|
12
|
+
const { policy } = await loadExecution(input);
|
|
13
|
+
return { executionId: policy.executionId, role: policy.role };
|
|
14
|
+
}
|
|
15
|
+
function executionRoot(input) {
|
|
16
|
+
return input.executionRoot ?? process.env.ALP_EXECUTION_ROOT ?? (0, node_path_1.join)(process.env.HOME ?? "", ".alp", "executions");
|
|
17
|
+
}
|
|
18
|
+
function assertExecutionId(id) {
|
|
19
|
+
if (!/^exec_[a-zA-Z0-9_-]+$/.test(id))
|
|
20
|
+
throw new Error("missing or invalid execution ID");
|
|
21
|
+
}
|
|
22
|
+
async function loadExecution(input) {
|
|
23
|
+
assertExecutionId(input.executionId);
|
|
24
|
+
const directory = (0, node_path_1.join)(executionRoot(input), input.executionId);
|
|
25
|
+
const [policy, state] = await Promise.all([
|
|
26
|
+
(0, promises_1.readFile)((0, node_path_1.join)(directory, "policy.json"), "utf8").then((value) => JSON.parse(value)),
|
|
27
|
+
(0, promises_1.readFile)((0, node_path_1.join)(directory, "state.json"), "utf8").then((value) => JSON.parse(value)),
|
|
28
|
+
]);
|
|
29
|
+
if (policy.executionId !== input.executionId || state.executionId !== input.executionId)
|
|
30
|
+
throw new Error("execution ID mismatch");
|
|
31
|
+
if (state.policyHash !== policy.policyHash)
|
|
32
|
+
throw new Error("execution state policy hash mismatch");
|
|
33
|
+
const definition = registry_1.agentRegistry.get(policy.role);
|
|
34
|
+
const expected = (0, execution_policy_1.createExecutionPolicy)({
|
|
35
|
+
executionId: policy.executionId,
|
|
36
|
+
definition,
|
|
37
|
+
workspace: policy.workspace,
|
|
38
|
+
workspaceMode: policy.workspaceMode,
|
|
39
|
+
createdAt: policy.createdAt,
|
|
40
|
+
});
|
|
41
|
+
if (JSON.stringify(expected) !== JSON.stringify(policy))
|
|
42
|
+
throw new Error("execution policy snapshot is invalid or stale");
|
|
43
|
+
return { policy, state };
|
|
44
|
+
}
|
|
45
|
+
/** Walk a still-running workflow forward to its terminal state so output can be submitted. */
|
|
46
|
+
function advanceToOutput(runner, definition, state) {
|
|
47
|
+
let candidate = state;
|
|
48
|
+
while (candidate.status === "running") {
|
|
49
|
+
const transitions = definition.workflow.states[candidate.currentState].transitions;
|
|
50
|
+
if (transitions.length !== 1)
|
|
51
|
+
throw new Error(`workflow state \`${candidate.currentState}\` has no unambiguous output path`);
|
|
52
|
+
candidate = runner.transition(definition.workflow, candidate, transitions[0]);
|
|
53
|
+
}
|
|
54
|
+
return candidate;
|
|
55
|
+
}
|
|
56
|
+
async function persistState(input, state) {
|
|
57
|
+
const file = (0, node_path_1.join)(executionRoot(input), input.executionId, "state.json");
|
|
58
|
+
const temporary = `${file}.${(0, node_crypto_1.randomUUID)()}.tmp`;
|
|
59
|
+
await (0, promises_1.writeFile)(temporary, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600, flag: "wx" });
|
|
60
|
+
await (0, promises_1.rename)(temporary, file);
|
|
61
|
+
await (0, promises_1.chmod)(file, 0o600);
|
|
62
|
+
}
|
|
63
|
+
async function finalizeExecution(input) {
|
|
64
|
+
const { policy, state } = await loadExecution(input);
|
|
65
|
+
const definition = registry_1.agentRegistry.get(policy.role);
|
|
66
|
+
if (state.workflow.status === "completed")
|
|
67
|
+
return { ok: true, status: "completed", issues: [] };
|
|
68
|
+
if (state.workflow.status === "failed")
|
|
69
|
+
return { ok: false, status: "failed", issues: ["output repair budget exhausted"] };
|
|
70
|
+
const runner = new workflow_runner_1.WorkflowRunner();
|
|
71
|
+
const workflow = advanceToOutput(runner, definition, state.workflow);
|
|
72
|
+
const result = runner.submitOutput(workflow, definition.output, input.output);
|
|
73
|
+
await persistState(input, {
|
|
74
|
+
...state,
|
|
75
|
+
status: result.state.status,
|
|
76
|
+
workflow: result.state,
|
|
77
|
+
...(result.validation.ok ? { output: result.validation.value ?? input.output } : {}),
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
ok: result.validation.ok,
|
|
81
|
+
status: result.state.status,
|
|
82
|
+
issues: result.validation.ok ? [] : result.validation.issues,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MarkdownFileStore = void 0;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const errors_1 = require("../errors");
|
|
7
|
+
const memory_service_1 = require("../memory-service");
|
|
8
|
+
const memory_path_mapper_1 = require("./memory-path-mapper");
|
|
9
|
+
const memory_grant_1 = require("../../agents/memory-grant");
|
|
10
|
+
const MEMORY_KINDS = new Set([
|
|
11
|
+
"fact",
|
|
12
|
+
"decision",
|
|
13
|
+
"reference",
|
|
14
|
+
"log",
|
|
15
|
+
"draft",
|
|
16
|
+
]);
|
|
17
|
+
let temporarySequence = 0;
|
|
18
|
+
function inferKind(content) {
|
|
19
|
+
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1];
|
|
20
|
+
const value = frontmatter?.match(/^kind:\s*([^\s#]+)\s*$/m)?.[1];
|
|
21
|
+
return value && MEMORY_KINDS.has(value) ? value : "reference";
|
|
22
|
+
}
|
|
23
|
+
async function pathExists(path) {
|
|
24
|
+
try {
|
|
25
|
+
await (0, promises_1.access)(path);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error.code === "ENOENT")
|
|
30
|
+
return false;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function atomicWrite(path, content) {
|
|
35
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(path), { recursive: true });
|
|
36
|
+
const temporary = (0, node_path_1.join)((0, node_path_1.dirname)(path), `.${(0, node_path_1.basename)(path)}.${process.pid}.${++temporarySequence}.tmp`);
|
|
37
|
+
try {
|
|
38
|
+
await (0, promises_1.writeFile)(temporary, content, { encoding: "utf8", mode: 0o600 });
|
|
39
|
+
await (0, promises_1.rename)(temporary, path);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await (0, promises_1.rm)(temporary, { force: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
class MarkdownFileStore {
|
|
46
|
+
mapper;
|
|
47
|
+
metadataPath;
|
|
48
|
+
now;
|
|
49
|
+
metadata;
|
|
50
|
+
initializing;
|
|
51
|
+
mutations = Promise.resolve();
|
|
52
|
+
constructor(options) {
|
|
53
|
+
this.mapper = new memory_path_mapper_1.MemoryPathMapper(options.root);
|
|
54
|
+
this.metadataPath = (0, node_path_1.join)(this.mapper.root, ".alp-memory-index.json");
|
|
55
|
+
this.now = options.now ?? (() => new Date());
|
|
56
|
+
}
|
|
57
|
+
async initialize() {
|
|
58
|
+
if (this.metadata)
|
|
59
|
+
return;
|
|
60
|
+
if (!this.initializing) {
|
|
61
|
+
this.initializing = (async () => {
|
|
62
|
+
await (0, promises_1.mkdir)(this.mapper.root, { recursive: true });
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(await (0, promises_1.readFile)(this.metadataPath, "utf8"));
|
|
65
|
+
if (parsed.formatVersion !== 1 || !parsed.entries || typeof parsed.entries !== "object") {
|
|
66
|
+
throw new Error("unsupported memory metadata index");
|
|
67
|
+
}
|
|
68
|
+
this.metadata = { formatVersion: 1, entries: { ...parsed.entries } };
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error.code !== "ENOENT")
|
|
72
|
+
throw error;
|
|
73
|
+
this.metadata = await this.rebuildMetadata();
|
|
74
|
+
await this.persistMetadata();
|
|
75
|
+
}
|
|
76
|
+
})();
|
|
77
|
+
}
|
|
78
|
+
await this.initializing;
|
|
79
|
+
}
|
|
80
|
+
async rebuildMetadata() {
|
|
81
|
+
const entries = {};
|
|
82
|
+
const addFile = async (id, path) => {
|
|
83
|
+
const [content, metadata] = await Promise.all([(0, promises_1.readFile)(path, "utf8"), (0, promises_1.stat)(path)]);
|
|
84
|
+
entries[id] = {
|
|
85
|
+
version: 1,
|
|
86
|
+
kind: inferKind(content),
|
|
87
|
+
createdAt: metadata.birthtime.toISOString(),
|
|
88
|
+
updatedAt: metadata.mtime.toISOString(),
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
const nestedMarkdown = async (directory, idPrefix) => {
|
|
92
|
+
let directoryMetadata;
|
|
93
|
+
try {
|
|
94
|
+
directoryMetadata = await (0, promises_1.lstat)(directory);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (error.code === "ENOENT")
|
|
98
|
+
return;
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
if (!directoryMetadata.isDirectory() || directoryMetadata.isSymbolicLink())
|
|
102
|
+
return;
|
|
103
|
+
for (const item of await (0, promises_1.readdir)(directory, { withFileTypes: true })) {
|
|
104
|
+
if (item.isFile() && (0, node_path_1.extname)(item.name) === ".md") {
|
|
105
|
+
await addFile([...idPrefix, (0, node_path_1.parse)(item.name).name].join(":"), (0, node_path_1.join)(directory, item.name));
|
|
106
|
+
}
|
|
107
|
+
else if (item.isDirectory() && !item.isSymbolicLink()) {
|
|
108
|
+
await nestedMarkdown((0, node_path_1.join)(directory, item.name), [...idPrefix, item.name]);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
await nestedMarkdown((0, node_path_1.join)(this.mapper.root, "shared"), ["shared"]);
|
|
113
|
+
for (const scope of ["projects", "private"]) {
|
|
114
|
+
const scopeRoot = (0, node_path_1.join)(this.mapper.root, scope);
|
|
115
|
+
let scopeMetadata;
|
|
116
|
+
try {
|
|
117
|
+
scopeMetadata = await (0, promises_1.lstat)(scopeRoot);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (error.code === "ENOENT")
|
|
121
|
+
continue;
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
if (!scopeMetadata.isDirectory() || scopeMetadata.isSymbolicLink())
|
|
125
|
+
continue;
|
|
126
|
+
for (const owner of await (0, promises_1.readdir)(scopeRoot, { withFileTypes: true })) {
|
|
127
|
+
if (!owner.isDirectory() || owner.isSymbolicLink())
|
|
128
|
+
continue;
|
|
129
|
+
await nestedMarkdown((0, node_path_1.join)(scopeRoot, owner.name), [scope === "projects" ? "project" : "private", owner.name]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { formatVersion: 1, entries };
|
|
133
|
+
}
|
|
134
|
+
async persistMetadata() {
|
|
135
|
+
await atomicWrite(this.metadataPath, `${JSON.stringify(this.metadata, null, 2)}\n`);
|
|
136
|
+
}
|
|
137
|
+
async exclusive(action) {
|
|
138
|
+
const result = this.mutations.then(action, action);
|
|
139
|
+
this.mutations = result.then(() => undefined, () => undefined);
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
async settledMetadata() {
|
|
143
|
+
await this.initialize();
|
|
144
|
+
await this.mutations;
|
|
145
|
+
return this.metadata;
|
|
146
|
+
}
|
|
147
|
+
async entry(id, metadata) {
|
|
148
|
+
const parsed = (0, memory_service_1.parseMemoryId)(id);
|
|
149
|
+
const path = await this.mapper.safePathFor(id);
|
|
150
|
+
let content;
|
|
151
|
+
try {
|
|
152
|
+
content = await (0, promises_1.readFile)(path, "utf8");
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
if (error.code === "ENOENT")
|
|
156
|
+
throw new errors_1.MemoryEntryNotFoundError(id);
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
return Object.freeze({
|
|
160
|
+
id,
|
|
161
|
+
scope: parsed.scope,
|
|
162
|
+
...(parsed.ownerRole === undefined ? {} : { ownerRole: parsed.ownerRole }),
|
|
163
|
+
...(parsed.projectId === undefined ? {} : { projectId: parsed.projectId }),
|
|
164
|
+
kind: metadata.kind,
|
|
165
|
+
content,
|
|
166
|
+
version: metadata.version,
|
|
167
|
+
createdAt: metadata.createdAt,
|
|
168
|
+
updatedAt: metadata.updatedAt,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
async search(query) {
|
|
172
|
+
const metadata = await this.settledMetadata();
|
|
173
|
+
const needle = query.text?.toLocaleLowerCase();
|
|
174
|
+
const results = [];
|
|
175
|
+
for (const id of Object.keys(metadata.entries).sort()) {
|
|
176
|
+
if (!(0, memory_grant_1.memoryGrantCovers)(query.scope, id))
|
|
177
|
+
continue;
|
|
178
|
+
const item = metadata.entries[id];
|
|
179
|
+
if (query.kinds && !query.kinds.includes(item.kind))
|
|
180
|
+
continue;
|
|
181
|
+
const entry = await this.entry(id, item);
|
|
182
|
+
if (needle && !`${entry.id}\n${entry.content}`.toLocaleLowerCase().includes(needle))
|
|
183
|
+
continue;
|
|
184
|
+
results.push(entry);
|
|
185
|
+
if (query.limit !== undefined && results.length >= query.limit)
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
return Object.freeze(results);
|
|
189
|
+
}
|
|
190
|
+
async get(id) {
|
|
191
|
+
(0, memory_service_1.parseMemoryId)(id);
|
|
192
|
+
const metadata = await this.settledMetadata();
|
|
193
|
+
const item = metadata.entries[id];
|
|
194
|
+
return item ? this.entry(id, item) : null;
|
|
195
|
+
}
|
|
196
|
+
async create(input) {
|
|
197
|
+
return this.exclusive(async () => {
|
|
198
|
+
await this.initialize();
|
|
199
|
+
const path = await this.mapper.safePathFor(input.id, true);
|
|
200
|
+
if (this.metadata.entries[input.id] || (await pathExists(path))) {
|
|
201
|
+
throw new errors_1.MemoryEntryAlreadyExistsError(input.id);
|
|
202
|
+
}
|
|
203
|
+
const timestamp = this.now().toISOString();
|
|
204
|
+
await atomicWrite(path, input.content);
|
|
205
|
+
const item = {
|
|
206
|
+
version: 1,
|
|
207
|
+
kind: input.kind,
|
|
208
|
+
createdAt: timestamp,
|
|
209
|
+
updatedAt: timestamp,
|
|
210
|
+
};
|
|
211
|
+
this.metadata.entries[input.id] = item;
|
|
212
|
+
await this.persistMetadata();
|
|
213
|
+
return this.entry(input.id, item);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
async update(id, input) {
|
|
217
|
+
return this.exclusive(async () => {
|
|
218
|
+
await this.initialize();
|
|
219
|
+
(0, memory_service_1.parseMemoryId)(id);
|
|
220
|
+
const current = this.metadata.entries[id];
|
|
221
|
+
if (!current)
|
|
222
|
+
throw new errors_1.MemoryEntryNotFoundError(id);
|
|
223
|
+
if (current.version !== input.expectedVersion) {
|
|
224
|
+
throw new errors_1.MemoryVersionConflictError(id, input.expectedVersion, current.version);
|
|
225
|
+
}
|
|
226
|
+
const path = await this.mapper.safePathFor(id);
|
|
227
|
+
const content = input.content ?? (await (0, promises_1.readFile)(path, "utf8"));
|
|
228
|
+
await atomicWrite(path, content);
|
|
229
|
+
const item = {
|
|
230
|
+
version: current.version + 1,
|
|
231
|
+
kind: input.kind ?? current.kind,
|
|
232
|
+
createdAt: current.createdAt,
|
|
233
|
+
updatedAt: this.now().toISOString(),
|
|
234
|
+
};
|
|
235
|
+
this.metadata.entries[id] = item;
|
|
236
|
+
await this.persistMetadata();
|
|
237
|
+
return this.entry(id, item);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async delete(id, expectedVersion) {
|
|
241
|
+
return this.exclusive(async () => {
|
|
242
|
+
await this.initialize();
|
|
243
|
+
(0, memory_service_1.parseMemoryId)(id);
|
|
244
|
+
const current = this.metadata.entries[id];
|
|
245
|
+
if (!current)
|
|
246
|
+
throw new errors_1.MemoryEntryNotFoundError(id);
|
|
247
|
+
if (current.version !== expectedVersion) {
|
|
248
|
+
throw new errors_1.MemoryVersionConflictError(id, expectedVersion, current.version);
|
|
249
|
+
}
|
|
250
|
+
const path = await this.mapper.safePathFor(id);
|
|
251
|
+
await (0, promises_1.rm)(path);
|
|
252
|
+
delete this.metadata.entries[id];
|
|
253
|
+
await this.persistMetadata();
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
exports.MarkdownFileStore = MarkdownFileStore;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemoryPathMapper = void 0;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const memory_service_1 = require("../memory-service");
|
|
7
|
+
function isWithin(root, target) {
|
|
8
|
+
const relation = (0, node_path_1.relative)(root, target);
|
|
9
|
+
return relation === "" || (!relation.startsWith("..") && !(0, node_path_1.isAbsolute)(relation));
|
|
10
|
+
}
|
|
11
|
+
async function nearestExistingDirectory(path) {
|
|
12
|
+
let candidate = path;
|
|
13
|
+
while (true) {
|
|
14
|
+
try {
|
|
15
|
+
const metadata = await (0, promises_1.lstat)(candidate);
|
|
16
|
+
if (metadata.isDirectory() || metadata.isSymbolicLink())
|
|
17
|
+
return candidate;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (error.code !== "ENOENT")
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
const parent = (0, node_path_1.dirname)(candidate);
|
|
24
|
+
if (parent === candidate)
|
|
25
|
+
return candidate;
|
|
26
|
+
candidate = parent;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
class MemoryPathMapper {
|
|
30
|
+
root;
|
|
31
|
+
constructor(root) {
|
|
32
|
+
this.root = (0, node_path_1.resolve)(root);
|
|
33
|
+
}
|
|
34
|
+
pathFor(id) {
|
|
35
|
+
const parsed = (0, memory_service_1.parseMemoryId)(id);
|
|
36
|
+
const directories = parsed.pathSegments.slice(0, -1);
|
|
37
|
+
const filename = `${parsed.pathSegments.at(-1)}.md`;
|
|
38
|
+
const path = parsed.scope === "shared"
|
|
39
|
+
? (0, node_path_1.join)(this.root, "shared", ...directories, filename)
|
|
40
|
+
: parsed.scope === "project"
|
|
41
|
+
? (0, node_path_1.join)(this.root, "projects", parsed.projectId, ...directories, filename)
|
|
42
|
+
: (0, node_path_1.join)(this.root, "private", parsed.ownerRole, ...directories, filename);
|
|
43
|
+
if (!isWithin(this.root, path))
|
|
44
|
+
throw new Error(`memory path escapes root for \`${id}\``);
|
|
45
|
+
return path;
|
|
46
|
+
}
|
|
47
|
+
async safePathFor(id, createParent = false) {
|
|
48
|
+
const path = this.pathFor(id);
|
|
49
|
+
await (0, promises_1.mkdir)(this.root, { recursive: true });
|
|
50
|
+
const rootReal = await (0, promises_1.realpath)(this.root);
|
|
51
|
+
const existingParent = await nearestExistingDirectory((0, node_path_1.dirname)(path));
|
|
52
|
+
const existingReal = await (0, promises_1.realpath)(existingParent);
|
|
53
|
+
if (!isWithin(rootReal, existingReal)) {
|
|
54
|
+
throw new Error(`memory path symlink escape for \`${id}\``);
|
|
55
|
+
}
|
|
56
|
+
if (createParent) {
|
|
57
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(path), { recursive: true });
|
|
58
|
+
const parentReal = await (0, promises_1.realpath)((0, node_path_1.dirname)(path));
|
|
59
|
+
if (!isWithin(rootReal, parentReal)) {
|
|
60
|
+
throw new Error(`memory path symlink escape for \`${id}\``);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const targetMetadata = await (0, promises_1.lstat)(path);
|
|
65
|
+
if (targetMetadata.isSymbolicLink()) {
|
|
66
|
+
throw new Error(`memory path symlink escape for \`${id}\``);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error.code !== "ENOENT")
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
return path;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.MemoryPathMapper = MemoryPathMapper;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RemoteApiStore = void 0;
|
|
4
|
+
class RemoteApiStore {
|
|
5
|
+
client;
|
|
6
|
+
constructor(client) {
|
|
7
|
+
this.client = client;
|
|
8
|
+
}
|
|
9
|
+
search(query) {
|
|
10
|
+
return this.client.search(query);
|
|
11
|
+
}
|
|
12
|
+
get(id) {
|
|
13
|
+
return this.client.get(id);
|
|
14
|
+
}
|
|
15
|
+
create(input) {
|
|
16
|
+
return this.client.create(input);
|
|
17
|
+
}
|
|
18
|
+
update(id, input) {
|
|
19
|
+
return this.client.update(id, input);
|
|
20
|
+
}
|
|
21
|
+
delete(id, expectedVersion) {
|
|
22
|
+
return this.client.delete(id, expectedVersion);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.RemoteApiStore = RemoteApiStore;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DeterministicContextRanker = void 0;
|
|
4
|
+
function matchScore(entry, queries) {
|
|
5
|
+
const terms = queries
|
|
6
|
+
.flatMap((query) => query.text?.toLocaleLowerCase().split(/\s+/) ?? [])
|
|
7
|
+
.filter(Boolean);
|
|
8
|
+
if (terms.length === 0)
|
|
9
|
+
return 0;
|
|
10
|
+
const haystack = `${entry.id}\n${entry.content}`.toLocaleLowerCase();
|
|
11
|
+
return terms.reduce((score, term) => score + (haystack.includes(term) ? 1 : 0), 0);
|
|
12
|
+
}
|
|
13
|
+
class DeterministicContextRanker {
|
|
14
|
+
rank(entries, queries) {
|
|
15
|
+
return [...entries].sort((left, right) => {
|
|
16
|
+
const score = matchScore(right, queries) - matchScore(left, queries);
|
|
17
|
+
return score || right.updatedAt.localeCompare(left.updatedAt) || left.id.localeCompare(right.id);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.DeterministicContextRanker = DeterministicContextRanker;
|