opendoor-plugin 0.2.0 → 0.5.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/README.md +222 -243
- package/dist/browser/pi-web-plugin.js +2121 -1452
- package/dist/package.json +6 -29
- package/dist/pi-extension/index.js +136 -0
- package/dist/server-plugin.js +54 -21
- package/package.json +19 -10
package/dist/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opendoor-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Opendoor — a pi-web browser plugin that surfaces Doorstop requirements stored in the workspace: the document tree, per-item state (reviewed/unreviewed changes, suspect links), validation findings, and one-click doorstop CLI actions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/kairibu/opendoor.git"
|
|
10
|
-
},
|
|
11
7
|
"keywords": [
|
|
12
8
|
"pi",
|
|
9
|
+
"pi-package",
|
|
13
10
|
"pi-web",
|
|
14
11
|
"plugin",
|
|
12
|
+
"pi-web.dev-plugin",
|
|
13
|
+
"pi-web-plugin",
|
|
15
14
|
"doorstop",
|
|
16
15
|
"requirements"
|
|
17
16
|
],
|
|
17
|
+
"main": "browser/pi-web-plugin.js",
|
|
18
18
|
"piWeb": {
|
|
19
19
|
"plugins": [
|
|
20
20
|
{
|
|
@@ -25,30 +25,7 @@
|
|
|
25
25
|
}
|
|
26
26
|
]
|
|
27
27
|
},
|
|
28
|
-
"files": [
|
|
29
|
-
"dist"
|
|
30
|
-
],
|
|
31
|
-
"main": "dist/browser/pi-web-plugin.js",
|
|
32
|
-
"scripts": {
|
|
33
|
-
"build": "node scripts/build-plugin.mjs",
|
|
34
|
-
"dev": "node scripts/build-plugin.mjs --watch",
|
|
35
|
-
"test": "vitest run",
|
|
36
|
-
"test:coverage": "vitest run --coverage",
|
|
37
|
-
"typecheck": "tsc --noEmit"
|
|
38
|
-
},
|
|
39
28
|
"peerDependencies": {
|
|
40
|
-
"@jmfederico/pi-web": ">=1.
|
|
41
|
-
},
|
|
42
|
-
"devDependencies": {
|
|
43
|
-
"@jmfederico/pi-web": "^1.202608.2",
|
|
44
|
-
"@types/js-yaml": "^4.0.9",
|
|
45
|
-
"@types/node": "^24.0.0",
|
|
46
|
-
"@vitest/coverage-v8": "^4.1.11",
|
|
47
|
-
"esbuild": "^0.27.7",
|
|
48
|
-
"happy-dom": "^20.11.1",
|
|
49
|
-
"js-yaml": "^4.3.2",
|
|
50
|
-
"lit": "^3.3.3",
|
|
51
|
-
"typescript": "^6.0.3",
|
|
52
|
-
"vitest": "^4.1.10"
|
|
29
|
+
"@jmfederico/pi-web": ">=1.202611.1"
|
|
53
30
|
}
|
|
54
31
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// Generated from src/pi-extension/opendoor-review.ts. Do not edit directly.
|
|
2
|
+
|
|
3
|
+
// src/doorstop-session-metadata.ts
|
|
4
|
+
var OPENDOOR_SESSION_METADATA_NAMESPACE = "opendoor";
|
|
5
|
+
var DOORSTOP_SESSION_METADATA_SCHEMA = 1;
|
|
6
|
+
var DOORSTOP_SESSION_METADATA_MAX_UIDS = 8;
|
|
7
|
+
var DOORSTOP_SESSION_METADATA_MAX_UID_LENGTH = 32;
|
|
8
|
+
var DOORSTOP_SESSION_METADATA_OPS = /* @__PURE__ */ new Set(["review"]);
|
|
9
|
+
var DOORSTOP_SESSION_METADATA_UID_PATTERN = /^[\w.-]+$/u;
|
|
10
|
+
function isMetadataUid(value) {
|
|
11
|
+
return typeof value === "string" && value !== "" && value.length <= DOORSTOP_SESSION_METADATA_MAX_UID_LENGTH && DOORSTOP_SESSION_METADATA_UID_PATTERN.test(value);
|
|
12
|
+
}
|
|
13
|
+
function buildDoorstopSessionMetadata(op, uids) {
|
|
14
|
+
if (!DOORSTOP_SESSION_METADATA_OPS.has(op)) {
|
|
15
|
+
throw new Error(`Unsupported doorstop session metadata op: ${JSON.stringify(op)}`);
|
|
16
|
+
}
|
|
17
|
+
if (uids.length === 0 || uids.length > DOORSTOP_SESSION_METADATA_MAX_UIDS) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`Doorstop session metadata requires 1..${String(DOORSTOP_SESSION_METADATA_MAX_UIDS)} UIDs`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
const copied = [];
|
|
23
|
+
for (const uid of uids) {
|
|
24
|
+
if (!isMetadataUid(uid)) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Invalid doorstop session metadata UID (expected a non-empty safe token of at most ${String(DOORSTOP_SESSION_METADATA_MAX_UID_LENGTH)} characters)`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
copied.push(uid);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
schema: DOORSTOP_SESSION_METADATA_SCHEMA,
|
|
33
|
+
op,
|
|
34
|
+
uids: copied
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/pi-extension/opendoor-review.ts
|
|
39
|
+
var PI_WEB_SESSION_DISPATCH_CHANNEL = "pi-web:session-dispatch:v1";
|
|
40
|
+
var OPENDOOR_REVIEW_COMMAND = "opendoor-review";
|
|
41
|
+
var OPENDOOR_REVIEW_MESSAGE_CUSTOM_TYPE = "opendoor-review-dispatch";
|
|
42
|
+
var OPENDOOR_REVIEW_SESSION_NAME_PREFIX = "Doorstop review ";
|
|
43
|
+
function defaultReviewInstruction(uid) {
|
|
44
|
+
return `Check doorstop requirement ${uid}: run \`doorstop\` in the workspace root, inspect ${uid}'s child links and references, and when it is ready run \`doorstop review ${uid}\`.`;
|
|
45
|
+
}
|
|
46
|
+
function parseReviewCommandArgs(args) {
|
|
47
|
+
const trimmed = args.trim();
|
|
48
|
+
if (trimmed === "") return void 0;
|
|
49
|
+
const match = /^(\S+)(?:\s+([\s\S]*))?$/u.exec(trimmed);
|
|
50
|
+
if (match === null) return void 0;
|
|
51
|
+
const uid = match[1] ?? "";
|
|
52
|
+
if (uid === "") return void 0;
|
|
53
|
+
return { uid, notes: (match[2] ?? "").trim() };
|
|
54
|
+
}
|
|
55
|
+
function opendoorReviewExtension(pi) {
|
|
56
|
+
pi.registerCommand(OPENDOOR_REVIEW_COMMAND, {
|
|
57
|
+
description: "Start an independent Doorstop review conversation for a requirement UID (e.g. /opendoor-review REQ0001)",
|
|
58
|
+
handler: async (args) => {
|
|
59
|
+
await runOpendoorReview(pi, args);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async function runOpendoorReview(pi, args) {
|
|
64
|
+
const parsed = parseReviewCommandArgs(args);
|
|
65
|
+
if (parsed === void 0) {
|
|
66
|
+
report(
|
|
67
|
+
pi,
|
|
68
|
+
`Usage: /${OPENDOOR_REVIEW_COMMAND} <UID> [instruction\u2026] \u2014 the UID is a Doorstop requirement UID.`
|
|
69
|
+
);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
let metadata;
|
|
73
|
+
try {
|
|
74
|
+
metadata = {
|
|
75
|
+
[OPENDOOR_SESSION_METADATA_NAMESPACE]: buildDoorstopSessionMetadata("review", [parsed.uid])
|
|
76
|
+
};
|
|
77
|
+
} catch (error) {
|
|
78
|
+
report(pi, `Cannot start a Doorstop review session: ${formatError(error)}`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const prompt = parsed.notes === "" ? defaultReviewInstruction(parsed.uid) : parsed.notes;
|
|
82
|
+
const name = `${OPENDOOR_REVIEW_SESSION_NAME_PREFIX}${parsed.uid}`;
|
|
83
|
+
let accepted;
|
|
84
|
+
pi.events.emit(PI_WEB_SESSION_DISPATCH_CHANNEL, {
|
|
85
|
+
input: { prompt, name, metadata },
|
|
86
|
+
accept: (result) => {
|
|
87
|
+
accepted = result instanceof Promise ? result : Promise.resolve(result);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
if (accepted === void 0) {
|
|
91
|
+
report(
|
|
92
|
+
pi,
|
|
93
|
+
"Doorstop review dispatch requires PI WEB's session dispatch bridge. Update PI WEB and restart the session daemon when safe; no session was started."
|
|
94
|
+
);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const result = await accepted;
|
|
99
|
+
report(pi, `Started an independent Doorstop review session for ${parsed.uid}${spawnSuffix(result)}.`);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
report(
|
|
102
|
+
pi,
|
|
103
|
+
`The Doorstop review session for ${parsed.uid} was not started: ${formatError(error)}`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function report(pi, text) {
|
|
108
|
+
try {
|
|
109
|
+
pi.sendMessage({
|
|
110
|
+
customType: OPENDOOR_REVIEW_MESSAGE_CUSTOM_TYPE,
|
|
111
|
+
content: text,
|
|
112
|
+
display: true
|
|
113
|
+
});
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function spawnSuffix(result) {
|
|
118
|
+
if (typeof result !== "object" || result === null) return "";
|
|
119
|
+
const sessionId = result["sessionId"];
|
|
120
|
+
return typeof sessionId === "string" && sessionId !== "" ? ` (session ${sessionId})` : "";
|
|
121
|
+
}
|
|
122
|
+
function formatError(error) {
|
|
123
|
+
if (error instanceof Error && error.message !== "") return error.message;
|
|
124
|
+
const text = String(error);
|
|
125
|
+
return text === "" ? "unknown error" : text;
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
OPENDOOR_REVIEW_COMMAND,
|
|
129
|
+
OPENDOOR_REVIEW_MESSAGE_CUSTOM_TYPE,
|
|
130
|
+
OPENDOOR_REVIEW_SESSION_NAME_PREFIX,
|
|
131
|
+
PI_WEB_SESSION_DISPATCH_CHANNEL,
|
|
132
|
+
opendoorReviewExtension as default,
|
|
133
|
+
defaultReviewInstruction,
|
|
134
|
+
parseReviewCommandArgs,
|
|
135
|
+
runOpendoorReview
|
|
136
|
+
};
|
package/dist/server-plugin.js
CHANGED
|
@@ -993,10 +993,24 @@ async function probeContainsDoorstopMarker(root, signal) {
|
|
|
993
993
|
return walk(root, 0);
|
|
994
994
|
}
|
|
995
995
|
var plugin = {
|
|
996
|
-
apiVersion:
|
|
996
|
+
apiVersion: 5,
|
|
997
997
|
name: "Opendoor",
|
|
998
|
+
// Permanently no `requires`/`start()` — see the module header: the review
|
|
999
|
+
// dispatch moved to the Pi extension, so this provider-contributing entry
|
|
1000
|
+
// never approaches the host's provider-cycle rule (and never declares the
|
|
1001
|
+
// host catalog capability, which is prohibited for provider-contributing
|
|
1002
|
+
// entries).
|
|
998
1003
|
activate(context) {
|
|
999
|
-
return {
|
|
1004
|
+
return {
|
|
1005
|
+
workspaceProvider: createDoorstopWorkspaceProvider(context),
|
|
1006
|
+
// v3 onward (unchanged on v5): the six-operation dispatch lives at the
|
|
1007
|
+
// activation level, not on the provider (`WorkspaceProvider.request`
|
|
1008
|
+
// was removed). The v5 context carries a `scope` union; the dispatch
|
|
1009
|
+
// accepts only the workspace scope and rejects the rest.
|
|
1010
|
+
peer: {
|
|
1011
|
+
request: (request) => dispatchDoorstopOperation(context, request)
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1000
1014
|
}
|
|
1001
1015
|
};
|
|
1002
1016
|
var server_plugin_default = plugin;
|
|
@@ -1020,28 +1034,47 @@ function createDoorstopWorkspaceProvider(context) {
|
|
|
1020
1034
|
publicMetadata: { doorstop: true }
|
|
1021
1035
|
}
|
|
1022
1036
|
];
|
|
1023
|
-
},
|
|
1024
|
-
request: (request) => {
|
|
1025
|
-
switch (request.operation) {
|
|
1026
|
-
case DOORSTOP_RUN_OPERATION:
|
|
1027
|
-
return requestDoorstopBackend(context, request);
|
|
1028
|
-
case DOORSTOP_BASELINE_OPERATION:
|
|
1029
|
-
return requestDoorstopBaseline(context, request);
|
|
1030
|
-
case DOORSTOP_GIT_STATUS_OPERATION:
|
|
1031
|
-
return requestDoorstopGitStatus(context, request);
|
|
1032
|
-
case DOORSTOP_GIT_STAGE_OPERATION:
|
|
1033
|
-
return requestDoorstopGitStage(context, request);
|
|
1034
|
-
case DOORSTOP_GIT_UNSTAGE_OPERATION:
|
|
1035
|
-
return requestDoorstopGitUnstage(context, request);
|
|
1036
|
-
case DOORSTOP_GIT_COMMIT_OPERATION:
|
|
1037
|
-
return requestDoorstopGitCommit(context, request);
|
|
1038
|
-
default:
|
|
1039
|
-
return Promise.reject(unsupportedBackendOperationError(request.operation));
|
|
1040
|
-
}
|
|
1041
1037
|
}
|
|
1042
1038
|
});
|
|
1043
1039
|
}
|
|
1040
|
+
function dispatchDoorstopOperation(context, request) {
|
|
1041
|
+
const { scope, operation, input, signal } = request;
|
|
1042
|
+
if (scope.kind !== "workspace") {
|
|
1043
|
+
return Promise.reject(unsupportedPeerScopeError(operation, scope.kind));
|
|
1044
|
+
}
|
|
1045
|
+
const handlerRequest = {
|
|
1046
|
+
project: scope.project,
|
|
1047
|
+
workspace: scope.workspace,
|
|
1048
|
+
operation,
|
|
1049
|
+
input,
|
|
1050
|
+
signal
|
|
1051
|
+
};
|
|
1052
|
+
switch (operation) {
|
|
1053
|
+
case DOORSTOP_RUN_OPERATION:
|
|
1054
|
+
return requestDoorstopBackend(context, handlerRequest);
|
|
1055
|
+
case DOORSTOP_BASELINE_OPERATION:
|
|
1056
|
+
return requestDoorstopBaseline(context, handlerRequest);
|
|
1057
|
+
case DOORSTOP_GIT_STATUS_OPERATION:
|
|
1058
|
+
return requestDoorstopGitStatus(context, handlerRequest);
|
|
1059
|
+
case DOORSTOP_GIT_STAGE_OPERATION:
|
|
1060
|
+
return requestDoorstopGitStage(context, handlerRequest);
|
|
1061
|
+
case DOORSTOP_GIT_UNSTAGE_OPERATION:
|
|
1062
|
+
return requestDoorstopGitUnstage(context, handlerRequest);
|
|
1063
|
+
case DOORSTOP_GIT_COMMIT_OPERATION:
|
|
1064
|
+
return requestDoorstopGitCommit(context, handlerRequest);
|
|
1065
|
+
default:
|
|
1066
|
+
return Promise.reject(unsupportedBackendOperationError(operation));
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
var peerScopeKinds = /* @__PURE__ */ new Set(["machine", "project", "workspace"]);
|
|
1070
|
+
function unsupportedPeerScopeError(operation, scopeKind) {
|
|
1071
|
+
const op = /^[A-Za-z0-9.-]+$/.test(operation) ? operation : "unknown";
|
|
1072
|
+
const kind = peerScopeKinds.has(scopeKind) ? scopeKind : "unknown";
|
|
1073
|
+
return new Error(`opendoor: ${op} peer requests require workspace scope (got ${kind})`);
|
|
1074
|
+
}
|
|
1044
1075
|
export {
|
|
1045
1076
|
createDoorstopWorkspaceProvider,
|
|
1046
|
-
server_plugin_default as default
|
|
1077
|
+
server_plugin_default as default,
|
|
1078
|
+
dispatchDoorstopOperation,
|
|
1079
|
+
unsupportedPeerScopeError
|
|
1047
1080
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opendoor-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Opendoor — a pi-web browser plugin that surfaces Doorstop requirements stored in the workspace: the document tree, per-item state (reviewed/unreviewed changes, suspect links), validation findings, and one-click doorstop CLI actions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,18 +10,26 @@
|
|
|
10
10
|
},
|
|
11
11
|
"keywords": [
|
|
12
12
|
"pi",
|
|
13
|
+
"pi-package",
|
|
13
14
|
"pi-web",
|
|
14
15
|
"plugin",
|
|
16
|
+
"pi-web.dev-plugin",
|
|
17
|
+
"pi-web-plugin",
|
|
15
18
|
"doorstop",
|
|
16
19
|
"requirements"
|
|
17
20
|
],
|
|
21
|
+
"pi": {
|
|
22
|
+
"extensions": [
|
|
23
|
+
"./dist/pi-extension/index.js"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
18
26
|
"piWeb": {
|
|
19
27
|
"plugins": [
|
|
20
28
|
{
|
|
21
29
|
"id": "opendoor",
|
|
22
|
-
"browserRoot": "browser",
|
|
23
|
-
"module": "browser/pi-web-plugin.js",
|
|
24
|
-
"serverModule": "server-plugin.js"
|
|
30
|
+
"browserRoot": "dist/browser",
|
|
31
|
+
"module": "dist/browser/pi-web-plugin.js",
|
|
32
|
+
"serverModule": "dist/server-plugin.js"
|
|
25
33
|
}
|
|
26
34
|
]
|
|
27
35
|
},
|
|
@@ -37,18 +45,19 @@
|
|
|
37
45
|
"typecheck": "tsc --noEmit"
|
|
38
46
|
},
|
|
39
47
|
"peerDependencies": {
|
|
40
|
-
"@
|
|
48
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
49
|
+
"@jmfederico/pi-web": ">=1.202611.1"
|
|
41
50
|
},
|
|
42
51
|
"devDependencies": {
|
|
43
|
-
"@jmfederico/pi-web": "^1.
|
|
52
|
+
"@jmfederico/pi-web": "^1.202611.1",
|
|
44
53
|
"@types/js-yaml": "^4.0.9",
|
|
45
|
-
"@types/node": "^24.
|
|
46
|
-
"@vitest/coverage-v8": "^4.1.
|
|
54
|
+
"@types/node": "^24.19.2",
|
|
55
|
+
"@vitest/coverage-v8": "^4.1.12",
|
|
47
56
|
"esbuild": "^0.27.7",
|
|
48
|
-
"happy-dom": "^20.
|
|
57
|
+
"happy-dom": "^20.14.6",
|
|
49
58
|
"js-yaml": "^4.3.2",
|
|
50
59
|
"lit": "^3.3.3",
|
|
51
60
|
"typescript": "^6.0.3",
|
|
52
|
-
"vitest": "^4.1.
|
|
61
|
+
"vitest": "^4.1.12"
|
|
53
62
|
}
|
|
54
63
|
}
|